summaryrefslogtreecommitdiffstats
path: root/dom/media/eme
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/eme')
-rw-r--r--dom/media/eme/CDMCaps.cpp170
-rw-r--r--dom/media/eme/CDMCaps.h113
-rw-r--r--dom/media/eme/CDMProxy.h279
-rw-r--r--dom/media/eme/DecryptorProxyCallback.h54
-rw-r--r--dom/media/eme/DetailedPromise.cpp105
-rw-r--r--dom/media/eme/DetailedPromise.h73
-rw-r--r--dom/media/eme/EMEUtils.cpp97
-rw-r--r--dom/media/eme/EMEUtils.h107
-rw-r--r--dom/media/eme/MediaEncryptedEvent.cpp129
-rw-r--r--dom/media/eme/MediaEncryptedEvent.h67
-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.h67
-rw-r--r--dom/media/eme/MediaKeySession.cpp674
-rw-r--r--dom/media/eme/MediaKeySession.h137
-rw-r--r--dom/media/eme/MediaKeyStatusMap.cpp121
-rw-r--r--dom/media/eme/MediaKeyStatusMap.h97
-rw-r--r--dom/media/eme/MediaKeySystemAccess.cpp1135
-rw-r--r--dom/media/eme/MediaKeySystemAccess.h81
-rw-r--r--dom/media/eme/MediaKeySystemAccessManager.cpp340
-rw-r--r--dom/media/eme/MediaKeySystemAccessManager.h83
-rw-r--r--dom/media/eme/MediaKeys.cpp593
-rw-r--r--dom/media/eme/MediaKeys.h168
-rw-r--r--dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.cpp140
-rw-r--r--dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.h69
-rw-r--r--dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp467
-rw-r--r--dom/media/eme/mediadrm/MediaDrmCDMProxy.h184
-rw-r--r--dom/media/eme/mediadrm/MediaDrmProxySupport.cpp284
-rw-r--r--dom/media/eme/mediadrm/MediaDrmProxySupport.h67
-rw-r--r--dom/media/eme/mediadrm/moz.build19
-rw-r--r--dom/media/eme/moz.build45
32 files changed, 6164 insertions, 0 deletions
diff --git a/dom/media/eme/CDMCaps.cpp b/dom/media/eme/CDMCaps.cpp
new file mode 100644
index 000000000..20ff63bad
--- /dev/null
+++ b/dom/media/eme/CDMCaps.cpp
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/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..7749bdc67
--- /dev/null
+++ b/dom/media/eme/CDMCaps.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CDMCaps_h_
+#define CDMCaps_h_
+
+#include "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..f3f1add43
--- /dev/null
+++ b/dom/media/eme/CDMProxy.h
@@ -0,0 +1,279 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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..5893bea2e
--- /dev/null
+++ b/dom/media/eme/DetailedPromise.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "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(nsIGlobalObject* aGlobal,
+ const nsACString& aName,
+ Telemetry::ID aSuccessLatencyProbe,
+ Telemetry::ID aFailureLatencyProbe)
+ : DetailedPromise(aGlobal, aName)
+{
+ mSuccessLatencyProbe.Construct(aSuccessLatencyProbe);
+ mFailureLatencyProbe.Construct(aFailureLatencyProbe);
+}
+
+DetailedPromise::~DetailedPromise()
+{
+ // It would be nice to assert that mResponded is identical to
+ // GetPromiseState() == PromiseState::Rejected. But by now we've been
+ // unlinked, so don't have a reference to our actual JS Promise object
+ // anymore.
+ MaybeReportTelemetry(Failed);
+}
+
+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());
+
+ MaybeReportTelemetry(Failed);
+
+ 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();
+}
+
+/* static */ already_AddRefed<DetailedPromise>
+DetailedPromise::Create(nsIGlobalObject* aGlobal,
+ ErrorResult& aRv,
+ const nsACString& aName,
+ Telemetry::ID aSuccessLatencyProbe,
+ Telemetry::ID aFailureLatencyProbe)
+{
+ RefPtr<DetailedPromise> promise = new DetailedPromise(aGlobal, aName, aSuccessLatencyProbe, aFailureLatencyProbe);
+ promise->CreateWrapper(nullptr, aRv);
+ return aRv.Failed() ? nullptr : promise.forget();
+}
+
+void
+DetailedPromise::MaybeReportTelemetry(Status aStatus)
+{
+ if (mResponded) {
+ return;
+ }
+ mResponded = true;
+ if (!mSuccessLatencyProbe.WasPassed() || !mFailureLatencyProbe.WasPassed()) {
+ return;
+ }
+ uint32_t latency = (TimeStamp::Now() - mStartTime).ToMilliseconds();
+ EME_LOG("%s %s latency %ums reported via telemetry", mName.get(),
+ ((aStatus == Succeeded) ? "succcess" : "failure"), latency);
+ Telemetry::ID tid = (aStatus == Succeeded) ? mSuccessLatencyProbe.Value()
+ : mFailureLatencyProbe.Value();
+ Telemetry::Accumulate(tid, latency);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/eme/DetailedPromise.h b/dom/media/eme/DetailedPromise.h
new file mode 100644
index 000000000..83e40c022
--- /dev/null
+++ b/dom/media/eme/DetailedPromise.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __DetailedPromise_h__
+#define __DetailedPromise_h__
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/Telemetry.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);
+
+ static already_AddRefed<DetailedPromise>
+ Create(nsIGlobalObject* aGlobal, ErrorResult& aRv,
+ const nsACString& aName,
+ Telemetry::ID aSuccessLatencyProbe,
+ Telemetry::ID aFailureLatencyProbe);
+
+ template <typename T>
+ void MaybeResolve(const T& aArg)
+ {
+ EME_LOG("%s promise resolved", mName.get());
+ MaybeReportTelemetry(Succeeded);
+ 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);
+
+ explicit DetailedPromise(nsIGlobalObject* aGlobal,
+ const nsACString& aName,
+ Telemetry::ID aSuccessLatencyProbe,
+ Telemetry::ID aFailureLatencyProbe);
+ virtual ~DetailedPromise();
+
+ enum Status { Succeeded, Failed };
+ void MaybeReportTelemetry(Status aStatus);
+
+ nsCString mName;
+ bool mResponded;
+ TimeStamp mStartTime;
+ Optional<Telemetry::ID> mSuccessLatencyProbe;
+ Optional<Telemetry::ID> mFailureLatencyProbe;
+};
+
+} // 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..c248b3a24
--- /dev/null
+++ b/dom/media/eme/EMEUtils.cpp
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/EMEUtils.h"
+#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());
+ 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)
+{
+ ArrayData data = GetArrayBufferViewOrArrayBufferData(aBufferOrView);
+ aOutData.Clear();
+ if (!data.IsValid()) {
+ return;
+ }
+ aOutData.AppendElements(data.mData, data.mLength);
+}
+
+bool
+IsClearkeyKeySystem(const nsAString& aKeySystem)
+{
+ return !CompareUTF8toUTF16(kEMEKeySystemClearkey, aKeySystem);
+}
+
+bool
+IsPrimetimeKeySystem(const nsAString& aKeySystem)
+{
+ return !CompareUTF8toUTF16(kEMEKeySystemPrimetime, aKeySystem);
+}
+
+bool
+IsWidevineKeySystem(const nsAString& aKeySystem)
+{
+ return !CompareUTF8toUTF16(kEMEKeySystemWidevine, aKeySystem);
+}
+
+nsString
+KeySystemToGMPName(const nsAString& aKeySystem)
+{
+ if (IsPrimetimeKeySystem(aKeySystem)) {
+ return NS_LITERAL_STRING("gmp-eme-adobe");
+ }
+ 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();
+}
+
+CDMType
+ToCDMTypeTelemetryEnum(const nsString& aKeySystem)
+{
+ if (IsWidevineKeySystem(aKeySystem)) {
+ return CDMType::eWidevine;
+ } else if (IsClearkeyKeySystem(aKeySystem)) {
+ return CDMType::eClearKey;
+ } else if (IsPrimetimeKeySystem(aKeySystem)) {
+ return CDMType::ePrimetime;
+ }
+ return CDMType::eUnknown;
+}
+
+} // namespace mozilla
diff --git a/dom/media/eme/EMEUtils.h b/dom/media/eme/EMEUtils.h
new file mode 100644
index 000000000..1794f8462
--- /dev/null
+++ b/dom/media/eme/EMEUtils.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef EME_LOG_H_
+#define EME_LOG_H_
+
+#include "VideoUtils.h"
+#include "mozilla/Logging.h"
+#include "nsString.h"
+#include "nsTArray.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);
+
+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
+IsPrimetimeKeySystem(const nsAString& aKeySystem);
+
+bool
+IsWidevineKeySystem(const nsAString& aKeySystem);
+
+enum CDMType {
+ eClearKey = 0,
+ ePrimetime = 1,
+ eWidevine = 2,
+ eUnknown = 3
+};
+
+CDMType
+ToCDMTypeTelemetryEnum(const nsString& aKeySystem);
+
+} // 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..8e2595fcb
--- /dev/null
+++ b/dom/media/eme/MediaEncryptedEvent.cpp
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "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();
+ a.ComputeLengthAndData();
+ e->mInitData = ArrayBuffer::Create(aGlobal.Context(),
+ a.Length(),
+ a.Data());
+ 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..806fccad6
--- /dev/null
+++ b/dom/media/eme/MediaEncryptedEvent.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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..37c509e67
--- /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 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/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);
+ aEventInitDict.mMessage.ComputeLengthAndData();
+ e->mMessage = ArrayBuffer::Create(aGlobal.Context(),
+ aEventInitDict.mMessage.Length(),
+ aEventInitDict.mMessage.Data());
+ 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..b6cc86dfa
--- /dev/null
+++ b/dom/media/eme/MediaKeyMessageEvent.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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..d5eff3f77
--- /dev/null
+++ b/dom/media/eme/MediaKeySession.cpp
@@ -0,0 +1,674 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/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.
+
+ Telemetry::Accumulate(Telemetry::VIDEO_CDM_GENERATE_REQUEST_CALLED,
+ ToCDMTypeTelemetryEnum(mKeySystem));
+
+ // 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..40481df99
--- /dev/null
+++ b/dom/media/eme/MediaKeySession.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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..5af6ff535
--- /dev/null
+++ b/dom/media/eme/MediaKeyStatusMap.cpp
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/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..396ae500e
--- /dev/null
+++ b/dom/media/eme/MediaKeyStatusMap.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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..7007d3a03
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -0,0 +1,1135 @@
+/* -*- 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"
+#ifdef MOZ_WIDGET_ANDROID
+#include "FennecJNIWrappers.h"
+#endif
+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 });
+#ifdef MOZ_WIDGET_ANDROID
+ // Check if we can use MediaDrm for this keysystem.
+ if (!havePlugin) {
+ havePlugin = mozilla::java::MediaDrmProxy::IsSchemeSupported(aKeySystem);
+ }
+#endif
+ 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 (Preferences::GetBool("media.gmp-eme-adobe.visible", false)) {
+ if (IsPrimetimeKeySystem(aKeySystem)) {
+ if (!Preferences::GetBool("media.gmp-eme-adobe.enabled", false)) {
+ aOutMessage = NS_LITERAL_CSTRING("Adobe EME disabled");
+ return MediaKeySystemStatus::Cdm_disabled;
+ }
+#ifdef XP_WIN
+ // Win Vista and later only.
+ if (!IsVistaOrLater()) {
+ aOutMessage = NS_LITERAL_CSTRING("Minimum Windows version (Vista) not met for Adobe EME");
+ return MediaKeySystemStatus::Cdm_not_supported;
+ }
+#endif
+ return EnsureCDMInstalled(aKeySystem, aOutMessage);
+ }
+ }
+
+ if (IsWidevineKeySystem(aKeySystem)) {
+ if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
+#ifdef XP_WIN
+ // Win Vista and later only.
+ if (!IsVistaOrLater()) {
+ aOutMessage = NS_LITERAL_CSTRING("Minimum Windows version (Vista) not met for Widevine EME");
+ return MediaKeySystemStatus::Cdm_not_supported;
+ }
+#endif
+ if (!Preferences::GetBool("media.gmp-widevinecdm.enabled", false)) {
+ aOutMessage = NS_LITERAL_CSTRING("Widevine EME disabled");
+ return MediaKeySystemStatus::Cdm_disabled;
+ }
+ return EnsureCDMInstalled(aKeySystem, aOutMessage);
+#ifdef MOZ_WIDGET_ANDROID
+ } else if (Preferences::GetBool("media.mediadrm-widevinecdm.visible", false)) {
+ nsCString keySystem = NS_ConvertUTF16toUTF8(aKeySystem);
+ bool supported = mozilla::java::MediaDrmProxy::IsSchemeSupported(keySystem);
+ if (!supported) {
+ aOutMessage = NS_LITERAL_CSTRING("Widevine CDM is not available");
+ return MediaKeySystemStatus::Cdm_not_installed;
+ }
+ return MediaKeySystemStatus::Available;
+#endif
+ }
+ }
+
+ 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);
+#ifdef MOZ_WIDGET_ANDROID
+ widevine.mSessionTypes.AppendElement(MediaKeySessionType::Persistent_license);
+#endif
+ 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);
+ }
+#elif !defined(MOZ_WIDGET_ANDROID)
+ widevine.mMP4.SetCanDecrypt(EME_CODEC_AAC);
+#endif
+
+#if defined(MOZ_WIDGET_ANDROID)
+ using namespace mozilla::java;
+ // MediaDrm.isCryptoSchemeSupported only allows passing
+ // "video/mp4" or "video/webm" for mimetype string.
+ // See https://developer.android.com/reference/android/media/MediaDrm.html#isCryptoSchemeSupported(java.util.UUID, java.lang.String)
+ // for more detail.
+ typedef struct {
+ const nsCString& mMimeType;
+ const nsCString& mEMECodecType;
+ const char16_t* mCodecType;
+ KeySystemContainerSupport* mSupportType;
+ } DataForValidation;
+
+ DataForValidation validationList[] = {
+ { nsCString("video/mp4"), EME_CODEC_H264, MediaDrmProxy::AVC, &widevine.mMP4 },
+ { nsCString("audio/mp4"), EME_CODEC_AAC, MediaDrmProxy::AAC, &widevine.mMP4 },
+ { nsCString("video/webm"), EME_CODEC_VP8, MediaDrmProxy::VP8, &widevine.mWebM },
+ { nsCString("video/webm"), EME_CODEC_VP9, MediaDrmProxy::VP9, &widevine.mWebM},
+ { nsCString("audio/webm"), EME_CODEC_VORBIS, MediaDrmProxy::VORBIS, &widevine.mWebM},
+ { nsCString("audio/webm"), EME_CODEC_OPUS, MediaDrmProxy::OPUS, &widevine.mWebM},
+ };
+
+ for (const auto& data: validationList) {
+ if (MediaDrmProxy::IsCryptoSchemeSupported(kEMEKeySystemWidevine,
+ data.mMimeType)) {
+ if (MediaDrmProxy::CanDecode(data.mCodecType)) {
+ data.mSupportType->SetCanDecryptAndDecode(data.mEMECodecType);
+ } else {
+ data.mSupportType->SetCanDecrypt(data.mEMECodecType);
+ }
+ }
+ }
+#else
+ 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);
+#endif
+ keySystemConfigs.AppendElement(Move(widevine));
+ }
+ }
+ {
+ if (HavePluginForKeySystem(kEMEKeySystemPrimetime)) {
+ KeySystemConfig primetime;
+ primetime.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemPrimetime);
+ primetime.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
+ primetime.mPersistentState = KeySystemFeatureSupport::Required;
+ primetime.mDistinctiveIdentifier = KeySystemFeatureSupport::Required;
+ primetime.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
+ primetime.mMP4.SetCanDecryptAndDecode(EME_CODEC_AAC);
+ primetime.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
+ keySystemConfigs.AppendElement(Move(primetime));
+ }
+ }
+
+ 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..8fefc62ec
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccessManager.cpp
@@ -0,0 +1,340 @@
+/* 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
+#ifdef XP_MACOSX
+#include "nsCocoaFeatures.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) &&
+ !IsPrimetimeKeySystem(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 &&
+ (IsPrimetimeKeySystem(aKeySystem) || 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..eedd675e4
--- /dev/null
+++ b/dom/media/eme/MediaKeys.cpp
@@ -0,0 +1,593 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/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 "mozilla/Telemetry.h"
+#include "GMPCDMProxy.h"
+#ifdef MOZ_WIDGET_ANDROID
+#include "mozilla/MediaDrmCDMProxy.h"
+#endif
+#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;
+#ifdef MOZ_WIDGET_ANDROID
+ if (IsWidevineKeySystem(mKeySystem)) {
+ proxy = new MediaDrmCDMProxy(this,
+ mKeySystem,
+ mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
+ mConfig.mPersistentState == MediaKeysRequirement::Required);
+ } else
+#endif
+ {
+ 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);
+
+ Telemetry::Accumulate(Telemetry::VIDEO_CDM_CREATED, ToCDMTypeTelemetryEnum(mKeySystem));
+}
+
+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..a3dbf37df
--- /dev/null
+++ b/dom/media/eme/MediaKeys.h
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef 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/mediadrm/MediaDrmCDMCallbackProxy.cpp b/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.cpp
new file mode 100644
index 000000000..ee57afae9
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaDrmCDMCallbackProxy.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 {
+
+MediaDrmCDMCallbackProxy::MediaDrmCDMCallbackProxy(CDMProxy* aProxy)
+ : mProxy(aProxy)
+{
+
+}
+
+void
+MediaDrmCDMCallbackProxy::SetSessionId(uint32_t aToken,
+ const nsCString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mProxy->OnSetSessionId(aToken, NS_ConvertUTF8toUTF16(aSessionId));
+}
+
+void
+MediaDrmCDMCallbackProxy::ResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mProxy->OnResolveLoadSessionPromise(aPromiseId, aSuccess);
+}
+
+void
+MediaDrmCDMCallbackProxy::ResolvePromise(uint32_t aPromiseId)
+{
+ // Note: CDMProxy proxies this from non-main threads to main thread.
+ mProxy->ResolvePromise(aPromiseId);
+}
+
+void
+MediaDrmCDMCallbackProxy::RejectPromise(uint32_t aPromiseId,
+ nsresult aException,
+ const nsCString& aMessage)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mProxy->OnRejectPromise(aPromiseId, aException, aMessage);
+}
+
+void
+MediaDrmCDMCallbackProxy::SessionMessage(const nsCString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // For removing constness
+ nsTArray<uint8_t> message(aMessage);
+ mProxy->OnSessionMessage(NS_ConvertUTF8toUTF16(aSessionId), aMessageType, message);
+}
+
+void
+MediaDrmCDMCallbackProxy::ExpirationChange(const nsCString& aSessionId,
+ UnixTime aExpiryTime)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mProxy->OnExpirationChange(NS_ConvertUTF8toUTF16(aSessionId), aExpiryTime);
+}
+
+void
+MediaDrmCDMCallbackProxy::SessionClosed(const nsCString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ bool keyStatusesChange = false;
+ {
+ CDMCaps::AutoLock caps(mProxy->Capabilites());
+ keyStatusesChange = caps.RemoveKeysForSession(NS_ConvertUTF8toUTF16(aSessionId));
+ }
+ if (keyStatusesChange) {
+ mProxy->OnKeyStatusesChange(NS_ConvertUTF8toUTF16(aSessionId));
+ }
+ mProxy->OnSessionClosed(NS_ConvertUTF8toUTF16(aSessionId));
+}
+
+void
+MediaDrmCDMCallbackProxy::SessionError(const nsCString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsCString& aMessage)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mProxy->OnSessionError(NS_ConvertUTF8toUTF16(aSessionId),
+ aException,
+ aSystemCode,
+ NS_ConvertUTF8toUTF16(aMessage));
+}
+
+void
+MediaDrmCDMCallbackProxy::BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ BatchedKeyStatusChangedInternal(aSessionId, aKeyInfos);
+}
+
+void
+MediaDrmCDMCallbackProxy::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) {
+ mProxy->OnKeyStatusesChange(NS_ConvertUTF8toUTF16(aSessionId));
+ }
+}
+
+void
+MediaDrmCDMCallbackProxy::Decrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypted event");
+}
+
+} // namespace mozilla \ No newline at end of file
diff --git a/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.h b/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.h
new file mode 100644
index 000000000..29f9c8192
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MediaDrmCDMCallbackProxy_h_
+#define MediaDrmCDMCallbackProxy_h_
+
+#include "mozilla/CDMProxy.h"
+#include "mozilla/DecryptorProxyCallback.h"
+
+namespace mozilla {
+class CDMProxy;
+// Proxies call backs from the MediaDrmProxy -> MediaDrmProxySupport back to the MediaKeys
+// object on the main thread.
+// We used annotation calledFrom = "gecko" to ensure running on main thread.
+class MediaDrmCDMCallbackProxy : public DecryptorProxyCallback {
+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;
+
+ ~MediaDrmCDMCallbackProxy() {}
+
+private:
+ friend class MediaDrmCDMProxy;
+ explicit MediaDrmCDMCallbackProxy(CDMProxy* aProxy);
+
+ void BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos);
+ // Warning: Weak ref.
+ CDMProxy* mProxy;
+};
+
+} // namespace mozilla
+#endif \ No newline at end of file
diff --git a/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp b/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
new file mode 100644
index 000000000..a57d764e7
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
@@ -0,0 +1,467 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/MediaDrmCDMProxy.h"
+#include "MediaDrmCDMCallbackProxy.h"
+
+using namespace mozilla::java::sdk;
+
+namespace mozilla {
+
+MediaDrmSessionType
+ToMediaDrmSessionType(dom::MediaKeySessionType aSessionType)
+{
+ switch (aSessionType) {
+ case dom::MediaKeySessionType::Temporary: return kKeyStreaming;
+ case dom::MediaKeySessionType::Persistent_license: return kKeyOffline;
+ default: return kKeyStreaming;
+ };
+}
+
+MediaDrmCDMProxy::MediaDrmCDMProxy(dom::MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+ : CDMProxy(aKeys,
+ aKeySystem,
+ aDistinctiveIdentifierRequired,
+ aPersistentStateRequired)
+ , mCDM(nullptr)
+ , mShutdownCalled(false)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_COUNT_CTOR(MediaDrmCDMProxy);
+}
+
+MediaDrmCDMProxy::~MediaDrmCDMProxy()
+{
+ MOZ_COUNT_DTOR(MediaDrmCDMProxy);
+}
+
+void
+MediaDrmCDMProxy::Init(PromiseId aPromiseId,
+ const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aName,
+ bool aInPrivateBrowsing)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ EME_LOG("MediaDrmCDMProxy::Init (%s, %s) %s",
+ NS_ConvertUTF16toUTF8(aOrigin).get(),
+ NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
+ (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"));
+
+ // Create a thread to work with cdm.
+ if (!mOwnerThread) {
+ nsresult rv = NS_NewNamedThread("MDCDMThread", getter_AddRefs(mOwnerThread));
+ if (NS_FAILED(rv)) {
+ RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Couldn't create CDM thread MediaDrmCDMProxy::Init"));
+ return;
+ }
+ }
+
+ mCDM = mozilla::MakeUnique<MediaDrmProxySupport>(mKeySystem);
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod<uint32_t>(this,
+ &MediaDrmCDMProxy::md_Init,
+ aPromiseId));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+MediaDrmCDMProxy::CreateSession(uint32_t aCreateSessionToken,
+ MediaKeySessionType aSessionType,
+ PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ 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,
+ &MediaDrmCDMProxy::md_CreateSession,
+ data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+MediaDrmCDMProxy::LoadSession(PromiseId aPromiseId,
+ const nsAString& aSessionId)
+{
+ // TODO: Implement LoadSession.
+ RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Currently Fennec did not support LoadSession"));
+}
+
+void
+MediaDrmCDMProxy::SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert)
+{
+ // TODO: Implement SetServerCertificate.
+ RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Currently Fennec did not support SetServerCertificate"));
+}
+
+void
+MediaDrmCDMProxy::UpdateSession(const nsAString& aSessionId,
+ PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ nsAutoPtr<UpdateSessionData> data(new UpdateSessionData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ data->mResponse = Move(aResponse);
+
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<UpdateSessionData>>(this,
+ &MediaDrmCDMProxy::md_UpdateSession,
+ data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+MediaDrmCDMProxy::CloseSession(const nsAString& aSessionId,
+ PromiseId aPromiseId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ nsAutoPtr<SessionOpData> data(new SessionOpData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<nsAutoPtr<SessionOpData>>(this,
+ &MediaDrmCDMProxy::md_CloseSession,
+ data));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+MediaDrmCDMProxy::RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId)
+{
+ // TODO: Implement RemoveSession.
+ RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Currently Fennec did not support RemoveSession"));
+}
+
+void
+MediaDrmCDMProxy::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod(this, &MediaDrmCDMProxy::md_Shutdown));
+
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+ mOwnerThread->Shutdown();
+ mOwnerThread = nullptr;
+}
+
+void
+MediaDrmCDMProxy::Terminated()
+{
+ // TODO: Implement Terminated.
+ // Should find a way to handle the case when remote side MediaDrm crashed.
+}
+
+const nsCString&
+MediaDrmCDMProxy::GetNodeId() const
+{
+ return mNodeId;
+}
+
+void
+MediaDrmCDMProxy::OnSetSessionId(uint32_t aCreateSessionToken,
+ const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+
+ RefPtr<dom::MediaKeySession> session(mKeys->GetPendingSession(aCreateSessionToken));
+ if (session) {
+ session->SetSessionId(aSessionId);
+ }
+}
+
+void
+MediaDrmCDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ mKeys->OnSessionLoaded(aPromiseId, aSuccess);
+}
+
+void
+MediaDrmCDMProxy::OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ nsTArray<uint8_t>& aMessage)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyMessage(aMessageType, aMessage);
+ }
+}
+
+void
+MediaDrmCDMProxy::OnExpirationChange(const nsAString& aSessionId,
+ UnixTime aExpiryTime)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->SetExpiration(static_cast<double>(aExpiryTime));
+ }
+}
+
+void
+MediaDrmCDMProxy::OnSessionClosed(const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->OnClosed();
+ }
+}
+
+void
+MediaDrmCDMProxy::OnSessionError(const nsAString& aSessionId,
+ nsresult aException,
+ uint32_t aSystemCode,
+ const nsAString& aMsg)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyError(aSystemCode);
+ }
+}
+
+void
+MediaDrmCDMProxy::OnRejectPromise(uint32_t aPromiseId,
+ nsresult aDOMException,
+ const nsCString& aMsg)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ RejectPromise(aPromiseId, aDOMException, aMsg);
+}
+
+RefPtr<MediaDrmCDMProxy::DecryptPromise>
+MediaDrmCDMProxy::Decrypt(MediaRawData* aSample)
+{
+ MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypting individually");
+ return nullptr;
+}
+
+void
+MediaDrmCDMProxy::OnDecrypted(uint32_t aId,
+ DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData)
+{
+ MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypted event");
+}
+
+void
+MediaDrmCDMProxy::RejectPromise(PromiseId aId, 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
+MediaDrmCDMProxy::ResolvePromise(PromiseId aId)
+{
+ if (NS_IsMainThread()) {
+ if (!mKeys.IsNull()) {
+ mKeys->ResolvePromise(aId);
+ } else {
+ NS_WARNING("MediaDrmCDMProxy unable to resolve promise!");
+ }
+ } else {
+ nsCOMPtr<nsIRunnable> task;
+ task = NewRunnableMethod<PromiseId>(this,
+ &MediaDrmCDMProxy::ResolvePromise,
+ aId);
+ NS_DispatchToMainThread(task);
+ }
+}
+
+const nsString&
+MediaDrmCDMProxy::KeySystem() const
+{
+ return mKeySystem;
+}
+
+CDMCaps&
+MediaDrmCDMProxy::Capabilites()
+{
+ return mCapabilites;
+}
+
+void
+MediaDrmCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyStatusesChange();
+ }
+}
+
+void
+MediaDrmCDMProxy::GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+ nsTArray<nsCString>& aSessionIds)
+{
+ CDMCaps::AutoLock caps(Capabilites());
+ caps.GetSessionIdsForKeyId(aKeyId, aSessionIds);
+}
+
+#ifdef DEBUG
+bool
+MediaDrmCDMProxy::IsOnOwnerThread()
+{
+ return NS_GetCurrentThread() == mOwnerThread;
+}
+#endif
+
+void
+MediaDrmCDMProxy::OnCDMCreated(uint32_t aPromiseId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+
+ if (mCDM) {
+ mKeys->OnCDMCreated(aPromiseId, GetNodeId(), 0);
+ return;
+ }
+
+ // No CDM? Just reject the promise.
+ mKeys->RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in OnCDMCreated()"));
+}
+
+void
+MediaDrmCDMProxy::md_Init(uint32_t aPromiseId)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ MOZ_ASSERT(mCDM);
+
+ mCallback = new MediaDrmCDMCallbackProxy(this);
+ mCDM->Init(mCallback);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<uint32_t>(this,
+ &MediaDrmCDMProxy::OnCDMCreated,
+ aPromiseId));
+ NS_DispatchToMainThread(task);
+}
+
+void
+MediaDrmCDMProxy::md_CreateSession(nsAutoPtr<CreateSessionData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in md_CreateSession"));
+ return;
+ }
+
+ mCDM->CreateSession(aData->mCreateSessionToken,
+ aData->mPromiseId,
+ aData->mInitDataType,
+ aData->mInitData,
+ ToMediaDrmSessionType(aData->mSessionType));
+}
+
+void
+MediaDrmCDMProxy::md_UpdateSession(nsAutoPtr<UpdateSessionData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in md_UpdateSession"));
+ return;
+ }
+ mCDM->UpdateSession(aData->mPromiseId,
+ aData->mSessionId,
+ aData->mResponse);
+}
+
+void
+MediaDrmCDMProxy::md_CloseSession(nsAutoPtr<SessionOpData> aData)
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+ NS_LITERAL_CSTRING("Null CDM in md_CloseSession"));
+ return;
+ }
+ mCDM->CloseSession(aData->mPromiseId, aData->mSessionId);
+}
+
+void
+MediaDrmCDMProxy::md_Shutdown()
+{
+ MOZ_ASSERT(IsOnOwnerThread());
+ MOZ_ASSERT(mCDM);
+ if (mShutdownCalled) {
+ return;
+ }
+ mShutdownCalled = true;
+ mCDM->Shutdown();
+ mCDM = nullptr;
+}
+
+} // namespace mozilla \ No newline at end of file
diff --git a/dom/media/eme/mediadrm/MediaDrmCDMProxy.h b/dom/media/eme/mediadrm/MediaDrmCDMProxy.h
new file mode 100644
index 000000000..c7cb3000f
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.h
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MediaDrmCDMProxy_h_
+#define MediaDrmCDMProxy_h_
+
+#include <jni.h>
+#include "mozilla/jni/Types.h"
+#include "GeneratedJNINatives.h"
+
+#include "mozilla/CDMProxy.h"
+#include "mozilla/CDMCaps.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/MediaDrmProxySupport.h"
+#include "mozilla/UniquePtr.h"
+
+#include "MediaCodec.h"
+#include "nsString.h"
+#include "nsAutoPtr.h"
+
+using namespace mozilla::java;
+
+namespace mozilla {
+class MediaDrmCDMCallbackProxy;
+class MediaDrmCDMProxy : public CDMProxy {
+public:
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDrmCDMProxy)
+
+ MediaDrmCDMProxy(dom::MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired);
+
+ void Init(PromiseId aPromiseId,
+ const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName,
+ bool aInPrivateBrowsing) override;
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ 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,
+ UnixTime 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 aCode,
+ 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 aCode,
+ const nsCString& aReason) override;
+
+ // Resolves promise with "undefined".
+ // Can be called from any thread.
+ void ResolvePromise(PromiseId aId) override;
+
+ // Threadsafe.
+ 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
+
+private:
+ virtual ~MediaDrmCDMProxy();
+
+ void OnCDMCreated(uint32_t aPromiseId);
+
+ struct CreateSessionData {
+ MediaKeySessionType mSessionType;
+ uint32_t mCreateSessionToken;
+ PromiseId mPromiseId;
+ nsCString mInitDataType;
+ nsTArray<uint8_t> mInitData;
+ };
+
+ struct UpdateSessionData {
+ PromiseId mPromiseId;
+ nsCString mSessionId;
+ nsTArray<uint8_t> mResponse;
+ };
+
+ struct SessionOpData {
+ PromiseId mPromiseId;
+ nsCString mSessionId;
+ };
+
+ class RejectPromiseTask : public Runnable {
+ public:
+ RejectPromiseTask(MediaDrmCDMProxy* aProxy,
+ PromiseId aId,
+ nsresult aCode,
+ const nsCString& aReason)
+ : mProxy(aProxy)
+ , mId(aId)
+ , mCode(aCode)
+ , mReason(aReason)
+ {
+ }
+ NS_METHOD Run() {
+ mProxy->RejectPromise(mId, mCode, mReason);
+ return NS_OK;
+ }
+ private:
+ RefPtr<MediaDrmCDMProxy> mProxy;
+ PromiseId mId;
+ nsresult mCode;
+ nsCString mReason;
+ };
+
+ nsCString mNodeId;
+ mozilla::UniquePtr<MediaDrmProxySupport> mCDM;
+ nsAutoPtr<MediaDrmCDMCallbackProxy> mCallback;
+ bool mShutdownCalled;
+
+// =====================================================================
+// For MediaDrmProxySupport
+ void md_Init(uint32_t aPromiseId);
+ void md_CreateSession(nsAutoPtr<CreateSessionData> aData);
+ void md_UpdateSession(nsAutoPtr<UpdateSessionData> aData);
+ void md_CloseSession(nsAutoPtr<SessionOpData> aData);
+ void md_Shutdown();
+// =====================================================================
+};
+
+} // namespace mozilla
+#endif // MediaDrmCDMProxy_h_ \ No newline at end of file
diff --git a/dom/media/eme/mediadrm/MediaDrmProxySupport.cpp b/dom/media/eme/mediadrm/MediaDrmProxySupport.cpp
new file mode 100644
index 000000000..192769470
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmProxySupport.cpp
@@ -0,0 +1,284 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaDrmProxySupport.h"
+#include "mozilla/EMEUtils.h"
+#include "FennecJNINatives.h"
+#include "MediaCodec.h" // For MediaDrm::KeyStatus
+#include "MediaPrefs.h"
+
+using namespace mozilla::java;
+
+namespace mozilla {
+
+LogModule* GetMDRMNLog() {
+ static LazyLogModule log("MediaDrmProxySupport");
+ return log;
+}
+
+class MediaDrmJavaCallbacksSupport
+ : public MediaDrmProxy::NativeMediaDrmProxyCallbacks::Natives<MediaDrmJavaCallbacksSupport>
+{
+public:
+ typedef MediaDrmProxy::NativeMediaDrmProxyCallbacks::Natives<MediaDrmJavaCallbacksSupport> MediaDrmProxyNativeCallbacks;
+ using MediaDrmProxyNativeCallbacks::DisposeNative;
+ using MediaDrmProxyNativeCallbacks::AttachNative;
+
+ MediaDrmJavaCallbacksSupport(DecryptorProxyCallback* aDecryptorProxyCallback)
+ : mDecryptorProxyCallback(aDecryptorProxyCallback)
+ {
+ MOZ_ASSERT(aDecryptorProxyCallback);
+ }
+ /*
+ * Native implementation, called by Java.
+ */
+ void OnSessionCreated(int aCreateSessionToken,
+ int aPromiseId,
+ jni::ByteArray::Param aSessionId,
+ jni::ByteArray::Param aRequest);
+
+ void OnSessionUpdated(int aPromiseId, jni::ByteArray::Param aSessionId);
+
+ void OnSessionClosed(int aPromiseId, jni::ByteArray::Param aSessionId);
+
+ void OnSessionMessage(jni::ByteArray::Param aSessionId,
+ int /*mozilla::dom::MediaKeyMessageType*/ aSessionMessageType,
+ jni::ByteArray::Param aRequest);
+
+ void OnSessionError(jni::ByteArray::Param aSessionId,
+ jni::String::Param aMessage);
+
+ void OnSessionBatchedKeyChanged(jni::ByteArray::Param,
+ jni::ObjectArray::Param);
+
+ void OnRejectPromise(int aPromiseId, jni::String::Param aMessage);
+
+private:
+ DecryptorProxyCallback* mDecryptorProxyCallback;
+}; // MediaDrmJavaCallbacksSupport
+
+void
+MediaDrmJavaCallbacksSupport::OnSessionCreated(int aCreateSessionToken,
+ int aPromiseId,
+ jni::ByteArray::Param aSessionId,
+ jni::ByteArray::Param aRequest)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ auto reqDataArray = aRequest->GetElements();
+ nsCString sessionId(reinterpret_cast<char*>(aSessionId->GetElements().Elements()),
+ aSessionId->Length());
+ MDRMN_LOG("SessionId(%s) closed", sessionId.get());
+
+ mDecryptorProxyCallback->SetSessionId(aCreateSessionToken, sessionId);
+ mDecryptorProxyCallback->ResolvePromise(aPromiseId);
+}
+
+void
+MediaDrmJavaCallbacksSupport::OnSessionUpdated(int aPromiseId,
+ jni::ByteArray::Param aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MDRMN_LOG("SessionId(%s) closed",
+ nsCString(reinterpret_cast<char*>(aSessionId->GetElements().Elements()),
+ aSessionId->Length()).get());
+ mDecryptorProxyCallback->ResolvePromise(aPromiseId);
+}
+
+void
+MediaDrmJavaCallbacksSupport::OnSessionClosed(int aPromiseId,
+ jni::ByteArray::Param aSessionId)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCString sessionId(reinterpret_cast<char*>(aSessionId->GetElements().Elements()),
+ aSessionId->Length());
+ MDRMN_LOG("SessionId(%s) closed", sessionId.get());
+ mDecryptorProxyCallback->ResolvePromise(aPromiseId);
+ mDecryptorProxyCallback->SessionClosed(sessionId);
+}
+
+void
+MediaDrmJavaCallbacksSupport::OnSessionMessage(jni::ByteArray::Param aSessionId,
+ int /*mozilla::dom::MediaKeyMessageType*/ aMessageType,
+ jni::ByteArray::Param aRequest)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCString sessionId(reinterpret_cast<char*>(aSessionId->GetElements().Elements()),
+ aSessionId->Length());
+ auto reqDataArray = aRequest->GetElements();
+
+ nsTArray<uint8_t> retRequest;
+ retRequest.AppendElements(reinterpret_cast<uint8_t*>(reqDataArray.Elements()),
+ reqDataArray.Length());
+
+ mDecryptorProxyCallback->SessionMessage(sessionId,
+ static_cast<dom::MediaKeyMessageType>(aMessageType),
+ retRequest);
+}
+
+void
+MediaDrmJavaCallbacksSupport::OnSessionError(jni::ByteArray::Param aSessionId,
+ jni::String::Param aMessage)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCString sessionId(reinterpret_cast<char*>(aSessionId->GetElements().Elements()),
+ aSessionId->Length());
+ nsCString errorMessage = aMessage->ToCString();
+ MDRMN_LOG("SessionId(%s)", sessionId.get());
+ // TODO: We cannot get system error code from media drm API.
+ // Currently use -1 as an error code.
+ mDecryptorProxyCallback->SessionError(sessionId,
+ NS_ERROR_DOM_INVALID_STATE_ERR,
+ -1,
+ errorMessage);
+}
+
+// TODO: MediaDrm.KeyStatus defined the status code not included
+// dom::MediaKeyStatus::Released and dom::MediaKeyStatus::Output_downscaled.
+// Should keep tracking for this if it will be changed in the future.
+static dom::MediaKeyStatus
+MediaDrmKeyStatusToMediaKeyStatus(int aStatusCode)
+{
+ using mozilla::java::sdk::KeyStatus;
+ switch (aStatusCode) {
+ case KeyStatus::STATUS_USABLE: return dom::MediaKeyStatus::Usable;
+ case KeyStatus::STATUS_EXPIRED: return dom::MediaKeyStatus::Expired;
+ case KeyStatus::STATUS_OUTPUT_NOT_ALLOWED: return dom::MediaKeyStatus::Output_restricted;
+ case KeyStatus::STATUS_INTERNAL_ERROR: return dom::MediaKeyStatus::Internal_error;
+ case KeyStatus::STATUS_PENDING: return dom::MediaKeyStatus::Status_pending;
+ default: return dom::MediaKeyStatus::Internal_error;
+ }
+}
+
+void
+MediaDrmJavaCallbacksSupport::OnSessionBatchedKeyChanged(jni::ByteArray::Param aSessionId,
+ jni::ObjectArray::Param aKeyInfos)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCString sessionId(reinterpret_cast<char*>(aSessionId->GetElements().Elements()),
+ aSessionId->Length());
+ nsTArray<jni::Object::LocalRef> keyInfosObjectArray(aKeyInfos->GetElements());
+
+ nsTArray<CDMKeyInfo> keyInfosArray;
+
+ for (auto&& keyInfoObject : keyInfosObjectArray) {
+ java::SessionKeyInfo::LocalRef keyInfo(mozilla::Move(keyInfoObject));
+ mozilla::jni::ByteArray::LocalRef keyIdByteArray = keyInfo->KeyId();
+ nsTArray<int8_t> keyIdInt8Array = keyIdByteArray->GetElements();
+ // Cast nsTArray<int8_t> to nsTArray<uint8_t>
+ nsTArray<uint8_t>* keyId = reinterpret_cast<nsTArray<uint8_t>*>(&keyIdInt8Array);
+ auto keyStatus = keyInfo->Status(); // int32_t
+ keyInfosArray.AppendElement(CDMKeyInfo(*keyId,
+ dom::Optional<dom::MediaKeyStatus>(
+ MediaDrmKeyStatusToMediaKeyStatus(keyStatus)
+ )
+ )
+ );
+ }
+
+ mDecryptorProxyCallback->BatchedKeyStatusChanged(sessionId,
+ keyInfosArray);
+}
+
+void
+MediaDrmJavaCallbacksSupport::OnRejectPromise(int aPromiseId, jni::String::Param aMessage)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCString reason = aMessage->ToCString();
+ MDRMN_LOG("OnRejectPromise aMessage(%s) ", reason.get());
+ // Current implementation assume all the reject from MediaDrm is due to invalid state.
+ // Other cases should be handled before calling into MediaDrmProxy API.
+ mDecryptorProxyCallback->RejectPromise(aPromiseId,
+ NS_ERROR_DOM_INVALID_STATE_ERR,
+ reason);
+}
+
+MediaDrmProxySupport::MediaDrmProxySupport(const nsAString& aKeySystem)
+ : mKeySystem(aKeySystem), mDestroyed(false)
+{
+ mJavaCallbacks = MediaDrmProxy::NativeMediaDrmProxyCallbacks::New();
+
+ mBridgeProxy =
+ MediaDrmProxy::Create(mKeySystem,
+ mJavaCallbacks,
+ MediaPrefs::PDMAndroidRemoteCodecEnabled());
+}
+
+MediaDrmProxySupport::~MediaDrmProxySupport()
+{
+ MOZ_ASSERT(mDestroyed, "Shutdown() should be called before !!");
+ MediaDrmJavaCallbacksSupport::DisposeNative(mJavaCallbacks);
+}
+
+nsresult
+MediaDrmProxySupport::Init(DecryptorProxyCallback* aCallback)
+{
+ MOZ_ASSERT(mJavaCallbacks);
+
+ mCallback = aCallback;
+ MediaDrmJavaCallbacksSupport::AttachNative(mJavaCallbacks,
+ mozilla::MakeUnique<MediaDrmJavaCallbacksSupport>(mCallback));
+ return mBridgeProxy != nullptr ? NS_OK : NS_ERROR_FAILURE;
+}
+
+void
+MediaDrmProxySupport::CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const nsCString& aInitDataType,
+ const nsTArray<uint8_t>& aInitData,
+ MediaDrmSessionType aSessionType)
+{
+ MOZ_ASSERT(mBridgeProxy);
+
+ auto initDataBytes =
+ mozilla::jni::ByteArray::New(reinterpret_cast<const int8_t*>(&aInitData[0]),
+ aInitData.Length());
+ // TODO: aSessionType is not used here.
+ // Refer to
+ // http://androidxref.com/5.1.1_r6/xref/external/chromium_org/media/base/android/java/src/org/chromium/media/MediaDrmBridge.java#420
+ // it is hard code to streaming type.
+ mBridgeProxy->CreateSession(aCreateSessionToken,
+ aPromiseId,
+ NS_ConvertUTF8toUTF16(aInitDataType),
+ initDataBytes);
+}
+
+void
+MediaDrmProxySupport::UpdateSession(uint32_t aPromiseId,
+ const nsCString& aSessionId,
+ const nsTArray<uint8_t>& aResponse)
+{
+ MOZ_ASSERT(mBridgeProxy);
+
+ auto response =
+ mozilla::jni::ByteArray::New(reinterpret_cast<const int8_t*>(aResponse.Elements()),
+ aResponse.Length());
+ mBridgeProxy->UpdateSession(aPromiseId,
+ NS_ConvertUTF8toUTF16(aSessionId),
+ response);
+}
+
+void
+MediaDrmProxySupport::CloseSession(uint32_t aPromiseId,
+ const nsCString& aSessionId)
+{
+ MOZ_ASSERT(mBridgeProxy);
+
+ mBridgeProxy->CloseSession(aPromiseId, NS_ConvertUTF8toUTF16(aSessionId));
+}
+
+void
+MediaDrmProxySupport::Shutdown()
+{
+ MOZ_ASSERT(mBridgeProxy);
+
+ if (mDestroyed) {
+ return;
+ }
+ mBridgeProxy->Destroy();
+ mDestroyed = true;
+}
+
+} // namespace mozilla \ No newline at end of file
diff --git a/dom/media/eme/mediadrm/MediaDrmProxySupport.h b/dom/media/eme/mediadrm/MediaDrmProxySupport.h
new file mode 100644
index 000000000..e43b71bc1
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmProxySupport.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MediaDrmProxySupport_H
+#define MediaDrmProxySupport_H
+
+#include "mozilla/DecryptorProxyCallback.h"
+#include "mozilla/Logging.h"
+#include "FennecJNIWrappers.h"
+#include "nsString.h"
+
+
+namespace mozilla {
+
+enum MediaDrmSessionType {
+ kKeyStreaming = 1,
+ kKeyOffline = 2,
+ kKeyRelease = 3,
+};
+
+#ifndef MDRMN_LOG
+ LogModule* GetMDRMNLog();
+ #define MDRMN_LOG(x, ...) MOZ_LOG(GetMDRMNLog(), mozilla::LogLevel::Debug,\
+ ("[MediaDrmProxySupport][%s]" x, __FUNCTION__, ##__VA_ARGS__))
+#endif
+
+class MediaDrmProxySupport final
+{
+public:
+
+ MediaDrmProxySupport(const nsAString& aKeySystem);
+ ~MediaDrmProxySupport();
+
+ /*
+ * APIs to act as GMPDecryptorAPI, discarding unnecessary calls.
+ */
+ nsresult Init(DecryptorProxyCallback* aCallback);
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ uint32_t aPromiseId,
+ const nsCString& aInitDataType,
+ const nsTArray<uint8_t>& aInitData,
+ MediaDrmSessionType aSessionType);
+
+ void UpdateSession(uint32_t aPromiseId,
+ const nsCString& aSessionId,
+ const nsTArray<uint8_t>& aResponse);
+
+ void CloseSession(uint32_t aPromiseId,
+ const nsCString& aSessionId);
+
+ void Shutdown();
+
+private:
+ const nsString mKeySystem;
+ java::MediaDrmProxy::GlobalRef mBridgeProxy;
+ java::MediaDrmProxy::NativeMediaDrmProxyCallbacks::GlobalRef mJavaCallbacks;
+ DecryptorProxyCallback* mCallback;
+ bool mDestroyed;
+
+};
+
+} // namespace mozilla
+#endif // MediaDrmProxySupport_H \ No newline at end of file
diff --git a/dom/media/eme/mediadrm/moz.build b/dom/media/eme/mediadrm/moz.build
new file mode 100644
index 000000000..c01f7a52e
--- /dev/null
+++ b/dom/media/eme/mediadrm/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 += [
+ 'MediaDrmCDMCallbackProxy.h',
+ 'MediaDrmCDMProxy.h',
+ 'MediaDrmProxySupport.h',
+]
+
+UNIFIED_SOURCES += [
+ 'MediaDrmCDMCallbackProxy.cpp',
+ 'MediaDrmCDMProxy.cpp',
+ 'MediaDrmProxySupport.cpp',
+]
+
+FINAL_LIBRARY = 'xul' \ No newline at end of file
diff --git a/dom/media/eme/moz.build b/dom/media/eme/moz.build
new file mode 100644
index 000000000..47e622ae3
--- /dev/null
+++ b/dom/media/eme/moz.build
@@ -0,0 +1,45 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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',
+]
+
+UNIFIED_SOURCES += [
+ 'CDMCaps.cpp',
+ 'DetailedPromise.cpp',
+ 'EMEUtils.cpp',
+ 'MediaEncryptedEvent.cpp',
+ 'MediaKeyError.cpp',
+ 'MediaKeyMessageEvent.cpp',
+ 'MediaKeys.cpp',
+ 'MediaKeySession.cpp',
+ 'MediaKeyStatusMap.cpp',
+ 'MediaKeySystemAccess.cpp',
+ 'MediaKeySystemAccessManager.cpp',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+ DIRS += ['mediadrm']
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'