summaryrefslogtreecommitdiffstats
path: root/dom/media/eme
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/media/eme/CDMCaps.cpp112
-rw-r--r--dom/media/eme/CDMCaps.h82
-rw-r--r--dom/media/eme/CDMProxy.h323
-rw-r--r--dom/media/eme/DecryptorProxyCallback.h54
-rw-r--r--dom/media/eme/DetailedPromise.cpp87
-rw-r--r--dom/media/eme/DetailedPromise.h104
-rw-r--r--dom/media/eme/EMEUtils.cpp119
-rw-r--r--dom/media/eme/EMEUtils.h106
-rw-r--r--dom/media/eme/KeySystemConfig.cpp213
-rw-r--r--dom/media/eme/KeySystemConfig.h157
-rw-r--r--dom/media/eme/KeySystemNames.h33
-rw-r--r--dom/media/eme/MediaEncryptedEvent.cpp108
-rw-r--r--dom/media/eme/MediaEncryptedEvent.h60
-rw-r--r--dom/media/eme/MediaKeyError.cpp27
-rw-r--r--dom/media/eme/MediaKeyError.h33
-rw-r--r--dom/media/eme/MediaKeyMessageEvent.cpp103
-rw-r--r--dom/media/eme/MediaKeyMessageEvent.h64
-rw-r--r--dom/media/eme/MediaKeySession.cpp622
-rw-r--r--dom/media/eme/MediaKeySession.h142
-rw-r--r--dom/media/eme/MediaKeyStatusMap.cpp99
-rw-r--r--dom/media/eme/MediaKeyStatusMap.h92
-rw-r--r--dom/media/eme/MediaKeySystemAccess.cpp1086
-rw-r--r--dom/media/eme/MediaKeySystemAccess.h81
-rw-r--r--dom/media/eme/MediaKeySystemAccessManager.cpp684
-rw-r--r--dom/media/eme/MediaKeySystemAccessManager.h229
-rw-r--r--dom/media/eme/MediaKeySystemAccessPermissionRequest.cpp91
-rw-r--r--dom/media/eme/MediaKeySystemAccessPermissionRequest.h74
-rw-r--r--dom/media/eme/MediaKeys.cpp844
-rw-r--r--dom/media/eme/MediaKeys.h236
-rw-r--r--dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.cpp115
-rw-r--r--dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.h63
-rw-r--r--dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp453
-rw-r--r--dom/media/eme/mediadrm/MediaDrmCDMProxy.h186
-rw-r--r--dom/media/eme/mediadrm/MediaDrmProxySupport.cpp272
-rw-r--r--dom/media/eme/mediadrm/MediaDrmProxySupport.h68
-rw-r--r--dom/media/eme/mediadrm/moz.build19
-rw-r--r--dom/media/eme/mediafoundation/WMFCDMImpl.cpp128
-rw-r--r--dom/media/eme/mediafoundation/WMFCDMImpl.h100
-rw-r--r--dom/media/eme/mediafoundation/WMFCDMProxy.cpp306
-rw-r--r--dom/media/eme/mediafoundation/WMFCDMProxy.h134
-rw-r--r--dom/media/eme/mediafoundation/WMFCDMProxyCallback.cpp72
-rw-r--r--dom/media/eme/mediafoundation/WMFCDMProxyCallback.h38
-rw-r--r--dom/media/eme/mediafoundation/moz.build21
-rw-r--r--dom/media/eme/moz.build54
44 files changed, 8094 insertions, 0 deletions
diff --git a/dom/media/eme/CDMCaps.cpp b/dom/media/eme/CDMCaps.cpp
new file mode 100644
index 0000000000..2752ada476
--- /dev/null
+++ b/dom/media/eme/CDMCaps.cpp
@@ -0,0 +1,112 @@
+/* -*- 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() = default;
+
+CDMCaps::~CDMCaps() = default;
+
+// 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::IsKeyUsable(const CencKeyId& aKeyId) {
+ for (const KeyStatus& keyStatus : mKeyStatuses) {
+ if (keyStatus.mId == aKeyId) {
+ return IsUsableStatus(keyStatus.mStatus);
+ }
+ }
+ return false;
+}
+
+bool CDMCaps::SetKeyStatus(const CencKeyId& aKeyId, const nsString& aSessionId,
+ const dom::Optional<dom::MediaKeyStatus>& aStatus) {
+ if (!aStatus.WasPassed()) {
+ // Called from ForgetKeyStatus.
+ // Return true if the element is found to notify key changes.
+ return mKeyStatuses.RemoveElement(
+ KeyStatus(aKeyId, aSessionId, dom::MediaKeyStatus::Internal_error));
+ }
+
+ KeyStatus key(aKeyId, aSessionId, aStatus.Value());
+ auto index = mKeyStatuses.IndexOf(key);
+ if (index != mKeyStatuses.NoIndex) {
+ if (mKeyStatuses[index].mStatus == aStatus.Value()) {
+ // No change.
+ return false;
+ }
+ auto oldStatus = mKeyStatuses[index].mStatus;
+ 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 {
+ 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 = 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::NotifyWhenKeyIdUsable(const CencKeyId& aKey,
+ SamplesWaitingForKey* aListener) {
+ MOZ_ASSERT(!IsKeyUsable(aKey));
+ MOZ_ASSERT(aListener);
+ mWaitForKeys.AppendElement(WaitForKeys(aKey, aListener));
+}
+
+void CDMCaps::GetKeyStatusesForSession(const nsAString& aSessionId,
+ nsTArray<KeyStatus>& aOutKeyStatuses) {
+ for (const KeyStatus& keyStatus : mKeyStatuses) {
+ if (keyStatus.mSessionId.Equals(aSessionId)) {
+ aOutKeyStatuses.AppendElement(keyStatus);
+ }
+ }
+}
+
+bool CDMCaps::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 0000000000..b16c5153b1
--- /dev/null
+++ b/dom/media/eme/CDMCaps.h
@@ -0,0 +1,82 @@
+/* -*- 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 "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.Clone()), mSessionId(aSessionId), mStatus(aStatus) {}
+ KeyStatus(const KeyStatus& aOther)
+ : mId(aOther.mId.Clone()),
+ 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;
+ };
+
+ 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);
+
+ // 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:
+ struct WaitForKeys {
+ WaitForKeys(const CencKeyId& aKeyId, SamplesWaitingForKey* aListener)
+ : mKeyId(aKeyId.Clone()), mListener(aListener) {}
+ CencKeyId mKeyId;
+ RefPtr<SamplesWaitingForKey> mListener;
+ };
+
+ 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 0000000000..e638be3358
--- /dev/null
+++ b/dom/media/eme/CDMProxy.h
@@ -0,0 +1,323 @@
+/* -*- 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/DataMutex.h"
+#include "mozilla/MozPromise.h"
+
+#include "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaKeys.h"
+
+#include "nsIThread.h"
+
+namespace mozilla {
+class ErrorResult;
+class MediaRawData;
+class ChromiumCDMProxy;
+#ifdef MOZ_WMF_CDM
+class WMFCDMProxy;
+#endif
+
+namespace eme {
+enum DecryptStatus {
+ Ok = 0,
+ GenericErr = 1,
+ NoKeyErr = 2,
+ AbortedErr = 3,
+};
+}
+
+using eme::DecryptStatus;
+
+struct DecryptResult {
+ DecryptResult(DecryptStatus aStatus, MediaRawData* aSample)
+ : mStatus(aStatus), mSample(aSample) {}
+ DecryptStatus mStatus;
+ RefPtr<MediaRawData> mSample;
+};
+
+typedef MozPromise<DecryptResult, DecryptResult, /* IsExclusive = */ true>
+ DecryptPromise;
+
+class CDMKeyInfo {
+ public:
+ explicit CDMKeyInfo(const nsTArray<uint8_t>& aKeyId)
+ : mKeyId(aKeyId.Clone()), mStatus() {}
+
+ CDMKeyInfo(const nsTArray<uint8_t>& aKeyId,
+ const dom::Optional<dom::MediaKeyStatus>& aStatus)
+ : mKeyId(aKeyId.Clone()), 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.Clone();
+ if (aKeyInfo.mStatus.WasPassed()) {
+ mStatus.Construct(aKeyInfo.mStatus.Value());
+ }
+ }
+
+ nsTArray<uint8_t> mKeyId;
+ dom::Optional<dom::MediaKeyStatus> mStatus;
+};
+
+// Time is defined as the number of milliseconds since the
+// Epoch (00:00:00 UTC, January 1, 1970).
+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_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ // 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) = 0;
+
+ // Main thread only.
+ // Uses the CDM to create a key session.
+ // Calls MediaKeys::OnSessionActivated() when session is created.
+ // Assumes ownership of (std::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,
+ dom::MediaKeySessionType aSessionType,
+ 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 (std::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 (std::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.
+ // Called to signal a request for output protection information from the CDM.
+ // This should forward the call up the stack where the query should be
+ // performed and then responded to via `NotifyOutputProtectionStatus`.
+ virtual void QueryOutputProtectionStatus() = 0;
+
+ // NotifyOutputProtectionStatus enums. Explicit values are specified to make
+ // it easy to match values in logs.
+ enum class OutputProtectionCheckStatus : uint8_t {
+ CheckFailed = 0,
+ CheckSuccessful = 1,
+ };
+
+ enum class OutputProtectionCaptureStatus : uint8_t {
+ CapturePossilbe = 0,
+ CaptureNotPossible = 1,
+ Unused = 2,
+ };
+ // End NotifyOutputProtectionStatus enums
+
+ // Main thread only.
+ // Notifies this proxy of the protection status for the media the CDM is
+ // associated with. This can be called in response to
+ // `QueryOutputProtectionStatus`, but can also be called without an
+ // associated query. In both cases the information will be forwarded to
+ // the CDM host machinery and used to handle requests from the CDM.
+ // @param aCheckStatus did the check succeed or not.
+ // @param aCaptureStatus if the check succeeded, this reflects if capture
+ // of media could take place. This doesn't mean capture is taking place.
+ // Callers should be conservative with this value such that it's okay to pass
+ // CapturePossilbe even if capture is not happening, but should never pass
+ // CaptureNotPossible if it could happen. If the check failed, this value is
+ // not used, and callers should pass Unused to indicate this.
+ virtual void NotifyOutputProtectionStatus(
+ OutputProtectionCheckStatus aCheckStatus,
+ OutputProtectionCaptureStatus aCaptureStatus) = 0;
+
+ // Main thread only.
+ virtual void Shutdown() = 0;
+
+ // Main thread only.
+ virtual void Terminated() = 0;
+
+ // Threadsafe.
+ const nsCString& GetNodeId() const { return mNodeId; };
+
+ // 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,
+ const 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, ErrorResult&& aException,
+ 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 the given ErrorResult.
+ //
+ // Can be called from any thread.
+ virtual void RejectPromise(PromiseId aId, ErrorResult&& aException,
+ const nsCString& aReason) = 0;
+
+ // Resolves promise with "undefined".
+ // Can be called from any thread.
+ virtual void ResolvePromise(PromiseId aId) = 0;
+
+ // Threadsafe.
+ const nsString& KeySystem() const { return mKeySystem; };
+
+ DataMutex<CDMCaps>& Capabilites() { return mCapabilites; };
+
+ // Main thread only.
+ virtual void OnKeyStatusesChange(const nsAString& aSessionId) = 0;
+
+ // Main thread only.
+ // Calls MediaKeys->ResolvePromiseWithKeyStatus(aPromiseId, aKeyStatus) after
+ // the CDM has processed the request.
+ virtual void GetStatusForPolicy(PromiseId aPromiseId,
+ const nsAString& aMinHdcpVersion) = 0;
+
+#ifdef DEBUG
+ virtual bool IsOnOwnerThread() = 0;
+#endif
+
+ virtual ChromiumCDMProxy* AsChromiumCDMProxy() { return nullptr; }
+
+#ifdef MOZ_WMF_CDM
+ virtual WMFCDMProxy* AsWMFCDMProxy() { return nullptr; }
+#endif
+
+ protected:
+ // Main thread only.
+ CDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem,
+ bool aDistinctiveIdentifierRequired, bool aPersistentStateRequired)
+ : mKeys(aKeys),
+ mKeySystem(aKeySystem),
+ mCapabilites("CDMProxy::mCDMCaps"),
+ mDistinctiveIdentifierRequired(aDistinctiveIdentifierRequired),
+ mPersistentStateRequired(aPersistentStateRequired),
+ mMainThread(GetMainThreadSerialEventTarget()) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ 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;
+
+ DataMutex<CDMCaps> mCapabilites;
+
+ const bool mDistinctiveIdentifierRequired;
+ const bool mPersistentStateRequired;
+
+ // The main thread associated with the root document.
+ const nsCOMPtr<nsISerialEventTarget> mMainThread;
+};
+
+} // namespace mozilla
+
+#endif // CDMProxy_h_
diff --git a/dom/media/eme/DecryptorProxyCallback.h b/dom/media/eme/DecryptorProxyCallback.h
new file mode 100644
index 0000000000..88acdd7967
--- /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"
+
+namespace mozilla {
+class ErrorResult;
+}
+
+class DecryptorProxyCallback {
+ public:
+ virtual ~DecryptorProxyCallback() {}
+
+ 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,
+ mozilla::ErrorResult&& 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 0000000000..0692a1dd07
--- /dev/null
+++ b/dom/media/eme/DetailedPromise.cpp
@@ -0,0 +1,87 @@
+/* -*- 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 "VideoUtils.h"
+#include "mozilla/dom/DOMException.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla::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::HistogramID aSuccessLatencyProbe,
+ Telemetry::HistogramID 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(kFailed);
+}
+
+void DetailedPromise::LogRejectionReason(uint32_t aErrorCode,
+ const nsACString& aReason) {
+ nsPrintfCString msg("%s promise rejected 0x%" PRIx32 " '%s'", mName.get(),
+ aErrorCode, PromiseFlatCString(aReason).get());
+ EME_LOG("%s", msg.get());
+
+ MaybeReportTelemetry(kFailed);
+
+ LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg));
+}
+
+void DetailedPromise::MaybeReject(nsresult aArg, const nsACString& aReason) {
+ LogRejectionReason(static_cast<uint32_t>(aArg), aReason);
+
+ Promise::MaybeRejectWithDOMException(aArg, aReason);
+}
+
+void DetailedPromise::MaybeReject(ErrorResult&& aArg,
+ const nsACString& aReason) {
+ LogRejectionReason(aArg.ErrorCodeAsInt(), aReason);
+ Promise::MaybeReject(std::move(aArg));
+}
+
+/* static */
+already_AddRefed<DetailedPromise> DetailedPromise::Create(
+ nsIGlobalObject* aGlobal, ErrorResult& aRv, const nsACString& aName) {
+ RefPtr<DetailedPromise> promise = new DetailedPromise(aGlobal, aName);
+ promise->CreateWrapper(aRv);
+ return aRv.Failed() ? nullptr : promise.forget();
+}
+
+void DetailedPromise::MaybeReportTelemetry(eStatus 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 == kSucceeded) ? "succcess" : "failure"), latency);
+ Telemetry::HistogramID tid = (aStatus == kSucceeded)
+ ? mSuccessLatencyProbe.Value()
+ : mFailureLatencyProbe.Value();
+ Telemetry::Accumulate(tid, latency);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/eme/DetailedPromise.h b/dom/media/eme/DetailedPromise.h
new file mode 100644
index 0000000000..02c774755f
--- /dev/null
+++ b/dom/media/eme/DetailedPromise.h
@@ -0,0 +1,104 @@
+/* -*- 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::dom {
+
+/*
+ * This is pretty horrible; bug 1160445.
+ * Extend Promise to add custom DOMException messages on rejection.
+ * Get rid of this once we've ironed out EME errors in the wild.
+ */
+class DetailedPromise : public Promise {
+ public:
+ static already_AddRefed<DetailedPromise> Create(nsIGlobalObject* aGlobal,
+ ErrorResult& aRv,
+ const nsACString& aName);
+
+ template <typename T>
+ void MaybeResolve(T&& aArg) {
+ EME_LOG("%s promise resolved", mName.get());
+ MaybeReportTelemetry(eStatus::kSucceeded);
+ Promise::MaybeResolve(std::forward<T>(aArg));
+ }
+
+ void MaybeReject(nsresult aArg) = delete;
+ void MaybeReject(nsresult aArg, const nsACString& aReason);
+
+ void MaybeReject(ErrorResult&& aArg) = delete;
+ void MaybeReject(ErrorResult&& aArg, const nsACString& aReason);
+
+ // Facilities for rejecting with various spec-defined exception values.
+#define DOMEXCEPTION(name, err) \
+ inline void MaybeRejectWith##name(const nsACString& aMessage) { \
+ LogRejectionReason(static_cast<uint32_t>(err), aMessage); \
+ Promise::MaybeRejectWith##name(aMessage); \
+ } \
+ template <int N> \
+ void MaybeRejectWith##name(const char(&aMessage)[N]) { \
+ MaybeRejectWith##name(nsLiteralCString(aMessage)); \
+ }
+
+#include "mozilla/dom/DOMExceptionNames.h"
+
+#undef DOMEXCEPTION
+
+ template <ErrNum errorNumber, typename... Ts>
+ void MaybeRejectWithTypeError(Ts&&... aMessageArgs) = delete;
+
+ inline void MaybeRejectWithTypeError(const nsACString& aMessage) {
+ ErrorResult res;
+ res.ThrowTypeError(aMessage);
+ MaybeReject(std::move(res), aMessage);
+ }
+
+ template <int N>
+ void MaybeRejectWithTypeError(const char (&aMessage)[N]) {
+ MaybeRejectWithTypeError(nsLiteralCString(aMessage));
+ }
+
+ template <ErrNum errorNumber, typename... Ts>
+ void MaybeRejectWithRangeError(Ts&&... aMessageArgs) = delete;
+
+ inline void MaybeRejectWithRangeError(const nsACString& aMessage) {
+ ErrorResult res;
+ res.ThrowRangeError(aMessage);
+ MaybeReject(std::move(res), aMessage);
+ }
+
+ template <int N>
+ void MaybeRejectWithRangeError(const char (&aMessage)[N]) {
+ MaybeRejectWithRangeError(nsLiteralCString(aMessage));
+ }
+
+ private:
+ explicit DetailedPromise(nsIGlobalObject* aGlobal, const nsACString& aName);
+
+ explicit DetailedPromise(nsIGlobalObject* aGlobal, const nsACString& aName,
+ Telemetry::HistogramID aSuccessLatencyProbe,
+ Telemetry::HistogramID aFailureLatencyProbe);
+ virtual ~DetailedPromise();
+
+ enum eStatus { kSucceeded, kFailed };
+ void MaybeReportTelemetry(eStatus aStatus);
+ void LogRejectionReason(uint32_t aErrorCode, const nsACString& aReason);
+
+ nsCString mName;
+ bool mResponded;
+ TimeStamp mStartTime;
+ Optional<Telemetry::HistogramID> mSuccessLatencyProbe;
+ Optional<Telemetry::HistogramID> mFailureLatencyProbe;
+};
+
+} // namespace mozilla::dom
+
+#endif // __DetailedPromise_h__
diff --git a/dom/media/eme/EMEUtils.cpp b/dom/media/eme/EMEUtils.cpp
new file mode 100644
index 0000000000..0f1630589e
--- /dev/null
+++ b/dom/media/eme/EMEUtils.cpp
@@ -0,0 +1,119 @@
+/* -*- 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 "jsfriendapi.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/dom/KeySystemNames.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());
+ JS::AutoCheckCannotGC nogc;
+ if (aBufferOrView.IsArrayBuffer()) {
+ const dom::ArrayBuffer& buffer = aBufferOrView.GetAsArrayBuffer();
+ buffer.ComputeState();
+ return ArrayData(buffer.Data(), buffer.Length());
+ } else if (aBufferOrView.IsArrayBufferView()) {
+ const dom::ArrayBufferView& bufferview =
+ aBufferOrView.GetAsArrayBufferView();
+ bufferview.ComputeState();
+ return ArrayData(bufferview.Data(), bufferview.Length());
+ }
+ return ArrayData(nullptr, 0);
+}
+
+void CopyArrayBufferViewOrArrayBufferData(
+ const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView,
+ nsTArray<uint8_t>& aOutData) {
+ JS::AutoCheckCannotGC nogc;
+ ArrayData data = GetArrayBufferViewOrArrayBufferData(aBufferOrView);
+ aOutData.Clear();
+ if (!data.IsValid()) {
+ return;
+ }
+ aOutData.AppendElements(data.mData, data.mLength);
+}
+
+bool IsClearkeyKeySystem(const nsAString& aKeySystem) {
+ if (StaticPrefs::media_clearkey_test_key_systems_enabled()) {
+ return aKeySystem.EqualsLiteral(kClearKeyKeySystemName) ||
+ aKeySystem.EqualsLiteral(kClearKeyWithProtectionQueryKeySystemName);
+ }
+ return aKeySystem.EqualsLiteral(kClearKeyKeySystemName);
+}
+
+bool IsWidevineKeySystem(const nsAString& aKeySystem) {
+ return aKeySystem.EqualsLiteral(kWidevineKeySystemName);
+}
+
+#ifdef MOZ_WMF_CDM
+bool IsPlayReadyKeySystemAndSupported(const nsAString& aKeySystem) {
+ if (!StaticPrefs::media_eme_playready_enabled()) {
+ return false;
+ }
+ // 1=enabled encrypted and clear, 2=enabled encrytped.
+ if (StaticPrefs::media_wmf_media_engine_enabled() != 1 &&
+ StaticPrefs::media_wmf_media_engine_enabled() != 2) {
+ return false;
+ }
+ return aKeySystem.EqualsLiteral(kPlayReadyKeySystemName) ||
+ aKeySystem.EqualsLiteral(kPlayReadyKeySystemHardware);
+}
+#endif
+
+nsString KeySystemToProxyName(const nsAString& aKeySystem) {
+ if (IsClearkeyKeySystem(aKeySystem)) {
+ return u"gmp-clearkey"_ns;
+ }
+ if (IsWidevineKeySystem(aKeySystem)) {
+ return u"gmp-widevinecdm"_ns;
+ }
+#ifdef MOZ_WMF_CDM
+ if (IsPlayReadyKeySystemAndSupported(aKeySystem)) {
+ return u"mfcdm-playready"_ns;
+ }
+#endif
+ MOZ_ASSERT_UNREACHABLE("Not supported key system!");
+ return u""_ns;
+}
+
+#define ENUM_TO_STR(enumVal) \
+ case enumVal: \
+ return #enumVal
+
+const char* ToMediaKeyStatusStr(dom::MediaKeyStatus aStatus) {
+ switch (aStatus) {
+ ENUM_TO_STR(dom::MediaKeyStatus::Usable);
+ ENUM_TO_STR(dom::MediaKeyStatus::Expired);
+ ENUM_TO_STR(dom::MediaKeyStatus::Released);
+ ENUM_TO_STR(dom::MediaKeyStatus::Output_restricted);
+ ENUM_TO_STR(dom::MediaKeyStatus::Output_downscaled);
+ ENUM_TO_STR(dom::MediaKeyStatus::Status_pending);
+ ENUM_TO_STR(dom::MediaKeyStatus::Internal_error);
+ default:
+ return "Undefined MediaKeyStatus!";
+ }
+}
+
+#undef ENUM_TO_STR
+
+} // namespace mozilla
diff --git a/dom/media/eme/EMEUtils.h b/dom/media/eme/EMEUtils.h
new file mode 100644
index 0000000000..2a9af09d58
--- /dev/null
+++ b/dom/media/eme/EMEUtils.h
@@ -0,0 +1,106 @@
+/* -*- 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 "mozilla/Logging.h"
+#include "mozilla/dom/MediaKeyStatusMapBinding.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 KeySystemToProxyName(const nsAString& aKeySystem);
+
+bool IsClearkeyKeySystem(const nsAString& aKeySystem);
+
+bool IsWidevineKeySystem(const nsAString& aKeySystem);
+
+#ifdef MOZ_WMF_CDM
+bool IsPlayReadyKeySystemAndSupported(const nsAString& aKeySystem);
+#endif
+
+// Note: Primetime is now unsupported, but we leave it in the enum so
+// that the telemetry enum values are not changed; doing so would break
+// existing telemetry probes.
+enum CDMType {
+ eClearKey = 0,
+ ePrimetime = 1, // Note: Unsupported.
+ eWidevine = 2,
+ eUnknown = 3
+};
+
+CDMType ToCDMTypeTelemetryEnum(const nsString& aKeySystem);
+
+const char* ToMediaKeyStatusStr(dom::MediaKeyStatus aStatus);
+
+} // namespace mozilla
+
+#endif // EME_LOG_H_
diff --git a/dom/media/eme/KeySystemConfig.cpp b/dom/media/eme/KeySystemConfig.cpp
new file mode 100644
index 0000000000..b8eacc7f9d
--- /dev/null
+++ b/dom/media/eme/KeySystemConfig.cpp
@@ -0,0 +1,213 @@
+/* -*- 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 "KeySystemConfig.h"
+
+#include "EMEUtils.h"
+#include "GMPUtils.h"
+#include "KeySystemNames.h"
+#include "mozilla/StaticPrefs_media.h"
+
+#ifdef XP_WIN
+# include "WMFDecoderModule.h"
+#endif
+#ifdef MOZ_WIDGET_ANDROID
+# include "AndroidDecoderModule.h"
+# include "mozilla/java/MediaDrmProxyWrappers.h"
+# include "nsMimeTypes.h"
+#endif
+
+#ifdef MOZ_WMF_CDM
+# include "mediafoundation/WMFCDMImpl.h"
+#endif
+
+namespace mozilla {
+
+/* static */
+bool KeySystemConfig::Supports(const nsAString& aKeySystem) {
+ nsCString api = nsLiteralCString(CHROMIUM_CDM_API);
+ nsCString name = NS_ConvertUTF16toUTF8(aKeySystem);
+
+ if (HaveGMPFor(api, {name})) {
+ return true;
+ }
+#ifdef MOZ_WIDGET_ANDROID
+ // Check if we can use MediaDrm for this keysystem.
+ if (mozilla::java::MediaDrmProxy::IsSchemeSupported(name)) {
+ return true;
+ }
+#endif
+#if MOZ_WMF_CDM
+ if (IsPlayReadyKeySystemAndSupported(aKeySystem) &&
+ WMFCDMImpl::Supports(aKeySystem)) {
+ return true;
+ }
+#endif
+ return false;
+}
+
+/* static */
+bool KeySystemConfig::GetConfig(const nsAString& aKeySystem,
+ KeySystemConfig& aConfig) {
+ if (!Supports(aKeySystem)) {
+ return false;
+ }
+
+ if (IsClearkeyKeySystem(aKeySystem)) {
+ aConfig.mKeySystem = aKeySystem;
+ aConfig.mInitDataTypes.AppendElement(u"cenc"_ns);
+ aConfig.mInitDataTypes.AppendElement(u"keyids"_ns);
+ aConfig.mInitDataTypes.AppendElement(u"webm"_ns);
+ aConfig.mPersistentState = Requirement::Optional;
+ aConfig.mDistinctiveIdentifier = Requirement::NotAllowed;
+ aConfig.mSessionTypes.AppendElement(SessionType::Temporary);
+ aConfig.mEncryptionSchemes.AppendElement(u"cenc"_ns);
+ aConfig.mEncryptionSchemes.AppendElement(u"cbcs"_ns);
+ aConfig.mEncryptionSchemes.AppendElement(u"cbcs-1-9"_ns);
+ if (StaticPrefs::media_clearkey_persistent_license_enabled()) {
+ aConfig.mSessionTypes.AppendElement(SessionType::PersistentLicense);
+ }
+#if defined(XP_WIN)
+ // Clearkey CDM uses WMF's H.264 decoder on Windows.
+ if (WMFDecoderModule::CanCreateMFTDecoder(WMFStreamType::H264)) {
+ aConfig.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
+ } else {
+ aConfig.mMP4.SetCanDecrypt(EME_CODEC_H264);
+ }
+#else
+ aConfig.mMP4.SetCanDecrypt(EME_CODEC_H264);
+#endif
+ aConfig.mMP4.SetCanDecrypt(EME_CODEC_AAC);
+ aConfig.mMP4.SetCanDecrypt(EME_CODEC_FLAC);
+ aConfig.mMP4.SetCanDecrypt(EME_CODEC_OPUS);
+ aConfig.mMP4.SetCanDecrypt(EME_CODEC_VP9);
+ aConfig.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
+ aConfig.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
+ aConfig.mWebM.SetCanDecrypt(EME_CODEC_VP8);
+ aConfig.mWebM.SetCanDecrypt(EME_CODEC_VP9);
+ return true;
+ }
+ if (IsWidevineKeySystem(aKeySystem)) {
+ aConfig.mKeySystem = aKeySystem;
+ aConfig.mInitDataTypes.AppendElement(u"cenc"_ns);
+ aConfig.mInitDataTypes.AppendElement(u"keyids"_ns);
+ aConfig.mInitDataTypes.AppendElement(u"webm"_ns);
+ aConfig.mPersistentState = Requirement::Optional;
+ aConfig.mDistinctiveIdentifier = Requirement::NotAllowed;
+ aConfig.mSessionTypes.AppendElement(SessionType::Temporary);
+#ifdef MOZ_WIDGET_ANDROID
+ aConfig.mSessionTypes.AppendElement(SessionType::PersistentLicense);
+#endif
+ aConfig.mAudioRobustness.AppendElement(u"SW_SECURE_CRYPTO"_ns);
+ aConfig.mVideoRobustness.AppendElement(u"SW_SECURE_CRYPTO"_ns);
+ aConfig.mVideoRobustness.AppendElement(u"SW_SECURE_DECODE"_ns);
+ aConfig.mEncryptionSchemes.AppendElement(u"cenc"_ns);
+ aConfig.mEncryptionSchemes.AppendElement(u"cbcs"_ns);
+ aConfig.mEncryptionSchemes.AppendElement(u"cbcs-1-9"_ns);
+
+#if defined(MOZ_WIDGET_ANDROID)
+ // 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;
+ KeySystemConfig::ContainerSupport* mSupportType;
+ } DataForValidation;
+
+ DataForValidation validationList[] = {
+ {nsCString(VIDEO_MP4), EME_CODEC_H264, java::MediaDrmProxy::AVC,
+ &aConfig.mMP4},
+ {nsCString(VIDEO_MP4), EME_CODEC_VP9, java::MediaDrmProxy::AVC,
+ &aConfig.mMP4},
+ {nsCString(AUDIO_MP4), EME_CODEC_AAC, java::MediaDrmProxy::AAC,
+ &aConfig.mMP4},
+ {nsCString(AUDIO_MP4), EME_CODEC_FLAC, java::MediaDrmProxy::FLAC,
+ &aConfig.mMP4},
+ {nsCString(AUDIO_MP4), EME_CODEC_OPUS, java::MediaDrmProxy::OPUS,
+ &aConfig.mMP4},
+ {nsCString(VIDEO_WEBM), EME_CODEC_VP8, java::MediaDrmProxy::VP8,
+ &aConfig.mWebM},
+ {nsCString(VIDEO_WEBM), EME_CODEC_VP9, java::MediaDrmProxy::VP9,
+ &aConfig.mWebM},
+ {nsCString(AUDIO_WEBM), EME_CODEC_VORBIS, java::MediaDrmProxy::VORBIS,
+ &aConfig.mWebM},
+ {nsCString(AUDIO_WEBM), EME_CODEC_OPUS, java::MediaDrmProxy::OPUS,
+ &aConfig.mWebM},
+ };
+
+ for (const auto& data : validationList) {
+ if (java::MediaDrmProxy::IsCryptoSchemeSupported(kWidevineKeySystemName,
+ data.mMimeType)) {
+ if (AndroidDecoderModule::SupportsMimeType(data.mMimeType) !=
+ media::DecodeSupport::Unsupported) {
+ data.mSupportType->SetCanDecryptAndDecode(data.mEMECodecType);
+ } else {
+ data.mSupportType->SetCanDecrypt(data.mEMECodecType);
+ }
+ }
+ }
+#else
+# 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 we assume Widevine
+ // will be used with AAC.
+ if (WMFDecoderModule::CanCreateMFTDecoder(WMFStreamType::AAC)) {
+ aConfig.mMP4.SetCanDecrypt(EME_CODEC_AAC);
+ }
+# else
+ aConfig.mMP4.SetCanDecrypt(EME_CODEC_AAC);
+# endif
+ aConfig.mMP4.SetCanDecrypt(EME_CODEC_FLAC);
+ aConfig.mMP4.SetCanDecrypt(EME_CODEC_OPUS);
+ aConfig.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
+ aConfig.mMP4.SetCanDecryptAndDecode(EME_CODEC_VP9);
+ aConfig.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
+ aConfig.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
+ aConfig.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP8);
+ aConfig.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP9);
+#endif
+ return true;
+ }
+#ifdef MOZ_WMF_CDM
+ if (IsPlayReadyKeySystemAndSupported(aKeySystem)) {
+ RefPtr<WMFCDMImpl> cdm = MakeRefPtr<WMFCDMImpl>(aKeySystem);
+ return cdm->GetCapabilities(aConfig);
+ }
+#endif
+ return false;
+}
+
+KeySystemConfig::SessionType ConvertToKeySystemConfigSessionType(
+ dom::MediaKeySessionType aType) {
+ switch (aType) {
+ case dom::MediaKeySessionType::Temporary:
+ return KeySystemConfig::SessionType::Temporary;
+ case dom::MediaKeySessionType::Persistent_license:
+ return KeySystemConfig::SessionType::PersistentLicense;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid session type");
+ return KeySystemConfig::SessionType::Temporary;
+ }
+}
+
+const char* SessionTypeToStr(KeySystemConfig::SessionType aType) {
+ switch (aType) {
+ case KeySystemConfig::SessionType::Temporary:
+ return "Temporary";
+ case KeySystemConfig::SessionType::PersistentLicense:
+ return "PersistentLicense";
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid session type");
+ return "Invalid";
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/eme/KeySystemConfig.h b/dom/media/eme/KeySystemConfig.h
new file mode 100644
index 0000000000..bf813b44e9
--- /dev/null
+++ b/dom/media/eme/KeySystemConfig.h
@@ -0,0 +1,157 @@
+/* -*- 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 DOM_MEDIA_EME_KEYSYSTEMCONFIG_H_
+#define DOM_MEDIA_EME_KEYSYSTEMCONFIG_H_
+
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/dom/MediaKeysBinding.h"
+
+namespace mozilla {
+
+struct KeySystemConfig {
+ public:
+ // EME MediaKeysRequirement:
+ // https://www.w3.org/TR/encrypted-media/#dom-mediakeysrequirement
+ enum class Requirement {
+ Required = 1,
+ Optional = 2,
+ NotAllowed = 3,
+ };
+
+ // EME MediaKeySessionType:
+ // https://www.w3.org/TR/encrypted-media/#dom-mediakeysessiontype
+ enum class SessionType {
+ Temporary = 1,
+ PersistentLicense = 2,
+ };
+
+ using EMECodecString = nsCString;
+ static constexpr auto EME_CODEC_AAC = "aac"_ns;
+ static constexpr auto EME_CODEC_OPUS = "opus"_ns;
+ static constexpr auto EME_CODEC_VORBIS = "vorbis"_ns;
+ static constexpr auto EME_CODEC_FLAC = "flac"_ns;
+ static constexpr auto EME_CODEC_H264 = "h264"_ns;
+ static constexpr auto EME_CODEC_VP8 = "vp8"_ns;
+ static constexpr auto EME_CODEC_VP9 = "vp9"_ns;
+
+ using EMEEncryptionSchemeString = nsCString;
+ static constexpr auto EME_ENCRYPTION_SCHEME_CENC = "cenc"_ns;
+ static constexpr auto EME_ENCRYPTION_SCHEME_CBCS = "cbcs"_ns;
+
+ // A codec can be decrypted-and-decoded by the CDM, or only decrypted
+ // by the CDM and decoded by Gecko. Not both.
+ struct ContainerSupport {
+ ContainerSupport() = default;
+ ~ContainerSupport() = default;
+ ContainerSupport(const ContainerSupport& aOther) {
+ mCodecsDecoded = aOther.mCodecsDecoded.Clone();
+ mCodecsDecrypted = aOther.mCodecsDecrypted.Clone();
+ }
+ ContainerSupport& operator=(const ContainerSupport& aOther) {
+ if (this == &aOther) {
+ return *this;
+ }
+ mCodecsDecoded = aOther.mCodecsDecoded.Clone();
+ mCodecsDecrypted = aOther.mCodecsDecrypted.Clone();
+ return *this;
+ }
+ ContainerSupport(ContainerSupport&& aOther) = default;
+ ContainerSupport& operator=(ContainerSupport&&) = default;
+
+ 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(const EMECodecString& aCodec) const {
+ return mCodecsDecoded.Contains(aCodec);
+ }
+
+ // CDM decrypts and passes the decrypted samples back to Gecko for decoding.
+ bool Decrypts(const EMECodecString& aCodec) const {
+ return mCodecsDecrypted.Contains(aCodec);
+ }
+
+ void SetCanDecryptAndDecode(const 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(const 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;
+ };
+
+ static bool Supports(const nsAString& aKeySystem);
+ static bool GetConfig(const nsAString& aKeySystem, KeySystemConfig& aConfig);
+
+ KeySystemConfig() = default;
+ ~KeySystemConfig() = default;
+ explicit KeySystemConfig(const KeySystemConfig& aOther) {
+ mKeySystem = aOther.mKeySystem;
+ mInitDataTypes = aOther.mInitDataTypes.Clone();
+ mPersistentState = aOther.mPersistentState;
+ mDistinctiveIdentifier = aOther.mDistinctiveIdentifier;
+ mSessionTypes = aOther.mSessionTypes.Clone();
+ mVideoRobustness = aOther.mVideoRobustness.Clone();
+ mAudioRobustness = aOther.mAudioRobustness.Clone();
+ mEncryptionSchemes = aOther.mEncryptionSchemes.Clone();
+ mMP4 = aOther.mMP4;
+ mWebM = aOther.mWebM;
+ }
+ KeySystemConfig& operator=(const KeySystemConfig& aOther) {
+ if (this == &aOther) {
+ return *this;
+ }
+ mKeySystem = aOther.mKeySystem;
+ mInitDataTypes = aOther.mInitDataTypes.Clone();
+ mPersistentState = aOther.mPersistentState;
+ mDistinctiveIdentifier = aOther.mDistinctiveIdentifier;
+ mSessionTypes = aOther.mSessionTypes.Clone();
+ mVideoRobustness = aOther.mVideoRobustness.Clone();
+ mAudioRobustness = aOther.mAudioRobustness.Clone();
+ mEncryptionSchemes = aOther.mEncryptionSchemes.Clone();
+ mMP4 = aOther.mMP4;
+ mWebM = aOther.mWebM;
+ return *this;
+ }
+ KeySystemConfig(KeySystemConfig&&) = default;
+ KeySystemConfig& operator=(KeySystemConfig&&) = default;
+
+ nsString mKeySystem;
+ nsTArray<nsString> mInitDataTypes;
+ Requirement mPersistentState = Requirement::NotAllowed;
+ Requirement mDistinctiveIdentifier = Requirement::NotAllowed;
+ nsTArray<SessionType> mSessionTypes;
+ nsTArray<nsString> mVideoRobustness;
+ nsTArray<nsString> mAudioRobustness;
+ nsTArray<nsString> mEncryptionSchemes;
+ ContainerSupport mMP4;
+ ContainerSupport mWebM;
+ bool mIsHW = false;
+};
+
+KeySystemConfig::SessionType ConvertToKeySystemConfigSessionType(
+ dom::MediaKeySessionType aType);
+const char* SessionTypeToStr(KeySystemConfig::SessionType aType);
+
+} // namespace mozilla
+
+#endif // DOM_MEDIA_EME_KEYSYSTEMCONFIG_H_
diff --git a/dom/media/eme/KeySystemNames.h b/dom/media/eme/KeySystemNames.h
new file mode 100644
index 0000000000..24b59f5d8e
--- /dev/null
+++ b/dom/media/eme/KeySystemNames.h
@@ -0,0 +1,33 @@
+/* -*- 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 DOM_MEDIA_EME_KEY_SYSTEM_NAMES_H_
+#define DOM_MEDIA_EME_KEY_SYSTEM_NAMES_H_
+
+// Header for key system names. Keep these separate from some of our other
+// EME utils because want to use these strings in contexts where other utils
+// may not build correctly. Specifically at time of writing:
+// - The GMP doesn't have prefs available, so we want to avoid utils that
+// touch the pref service.
+// - The clear key CDM links a limited subset of what normal Fx does, so we
+// need to avoid any utils that touch things like XUL.
+
+namespace mozilla {
+// EME Key System Strings.
+inline constexpr char kClearKeyKeySystemName[] = "org.w3.clearkey";
+inline constexpr char kClearKeyWithProtectionQueryKeySystemName[] =
+ "org.mozilla.clearkey_with_protection_query";
+inline constexpr char kWidevineKeySystemName[] = "com.widevine.alpha";
+#ifdef MOZ_WMF_CDM
+// https://learn.microsoft.com/en-us/playready/overview/key-system-strings
+inline constexpr char kPlayReadyKeySystemName[] =
+ "com.microsoft.playready.recommendation";
+inline constexpr char kPlayReadyKeySystemHardware[] =
+ "com.microsoft.playready.recommendation.3000";
+#endif
+} // namespace mozilla
+
+#endif // DOM_MEDIA_EME_KEY_SYSTEM_NAMES_H_
diff --git a/dom/media/eme/MediaEncryptedEvent.cpp b/dom/media/eme/MediaEncryptedEvent.cpp
new file mode 100644
index 0000000000..b011715295
--- /dev/null
+++ b/dom/media/eme/MediaEncryptedEvent.cpp
@@ -0,0 +1,108 @@
+/* -*- 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 "js/ArrayBuffer.h"
+#include "jsfriendapi.h"
+#include "nsINode.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/HoldDropJSObjects.h"
+
+namespace mozilla::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)
+ mozilla::DropJSObjects(tmp);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaEncryptedEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+MediaEncryptedEvent::MediaEncryptedEvent(EventTarget* aOwner)
+ : Event(aOwner, nullptr, nullptr) {
+ mozilla::HoldJSObjects(this);
+}
+
+MediaEncryptedEvent::~MediaEncryptedEvent() { mozilla::DropJSObjects(this); }
+
+JSObject* MediaEncryptedEvent::WrapObjectInternal(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return MediaEncryptedEvent_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<MediaEncryptedEvent> MediaEncryptedEvent::Constructor(
+ EventTarget* aOwner) {
+ RefPtr<MediaEncryptedEvent> e = new MediaEncryptedEvent(aOwner);
+ e->InitEvent(u"encrypted"_ns, CanBubble::eNo, Cancelable::eNo);
+ 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(u"encrypted"_ns, CanBubble::eNo, Cancelable::eNo);
+ e->mInitDataType = aInitDataType;
+ e->mRawInitData = aInitData.Clone();
+ 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()) {
+ JS::Rooted<JSObject*> buffer(aGlobal.Context(),
+ aEventInitDict.mInitData.Value().Obj());
+ e->mInitData = JS::CopyArrayBuffer(aGlobal.Context(), buffer);
+ if (!e->mInitData) {
+ aRv.NoteJSContextException(aGlobal.Context());
+ 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.NoteJSContextException(cx);
+ return;
+ }
+ mRawInitData.Clear();
+ }
+ aData.set(mInitData);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/eme/MediaEncryptedEvent.h b/dom/media/eme/MediaEncryptedEvent.h
new file mode 100644
index 0000000000..0fcb5db051
--- /dev/null
+++ b/dom/media/eme/MediaEncryptedEvent.h
@@ -0,0 +1,60 @@
+/* -*- 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 <cstdint>
+#include "js/RootingAPI.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/Event.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+struct MediaKeyNeededEventInit;
+
+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 mozilla::dom
+
+#endif // mozilla_dom_MediaKeyNeededEvent_h__
diff --git a/dom/media/eme/MediaKeyError.cpp b/dom/media/eme/MediaKeyError.cpp
new file mode 100644
index 0000000000..7172b0d05b
--- /dev/null
+++ b/dom/media/eme/MediaKeyError.cpp
@@ -0,0 +1,27 @@
+/* -*- 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::dom {
+
+MediaKeyError::MediaKeyError(EventTarget* aOwner, uint32_t aSystemCode)
+ : Event(aOwner, nullptr, nullptr), mSystemCode(aSystemCode) {
+ InitEvent(u"error"_ns, CanBubble::eNo, Cancelable::eNo);
+}
+
+MediaKeyError::~MediaKeyError() = default;
+
+uint32_t MediaKeyError::SystemCode() const { return mSystemCode; }
+
+JSObject* MediaKeyError::WrapObjectInternal(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MediaKeyError_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/eme/MediaKeyError.h b/dom/media/eme/MediaKeyError.h
new file mode 100644
index 0000000000..ef4ecf1375
--- /dev/null
+++ b/dom/media/eme/MediaKeyError.h
@@ -0,0 +1,33 @@
+/* -*- 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 "nsWrapperCache.h"
+#include "mozilla/dom/Event.h"
+#include "js/TypeDecls.h"
+
+namespace mozilla::dom {
+
+class MediaKeyError final : public Event {
+ public:
+ 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 mozilla::dom
+
+#endif
diff --git a/dom/media/eme/MediaKeyMessageEvent.cpp b/dom/media/eme/MediaKeyMessageEvent.cpp
new file mode 100644
index 0000000000..69ce5499f4
--- /dev/null
+++ b/dom/media/eme/MediaKeyMessageEvent.cpp
@@ -0,0 +1,103 @@
+/* -*- 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/ArrayBuffer.h"
+#include "js/RootingAPI.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::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)
+ mozilla::DropJSObjects(tmp);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeyMessageEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+MediaKeyMessageEvent::MediaKeyMessageEvent(EventTarget* aOwner)
+ : Event(aOwner, nullptr, nullptr),
+ mMessageType(static_cast<MediaKeyMessageType>(0)) {
+ mozilla::HoldJSObjects(this);
+}
+
+MediaKeyMessageEvent::~MediaKeyMessageEvent() { mozilla::DropJSObjects(this); }
+
+MediaKeyMessageEvent* MediaKeyMessageEvent::AsMediaKeyMessageEvent() {
+ return this;
+}
+
+JSObject* MediaKeyMessageEvent::WrapObjectInternal(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return MediaKeyMessageEvent_Binding::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(u"message"_ns, false, false);
+ e->mMessageType = aMessageType;
+ e->mRawMessage = aMessage.Clone();
+ 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);
+ JS::Rooted<JSObject*> buffer(aGlobal.Context(),
+ aEventInitDict.mMessage.Obj());
+ e->mMessage = JS::CopyArrayBuffer(aGlobal.Context(), buffer);
+ if (!e->mMessage) {
+ aRv.NoteJSContextException(aGlobal.Context());
+ 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.NoteJSContextException(cx);
+ return;
+ }
+ mRawMessage.Clear();
+ }
+ aMessage.set(mMessage);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/eme/MediaKeyMessageEvent.h b/dom/media/eme/MediaKeyMessageEvent.h
new file mode 100644
index 0000000000..400439b065
--- /dev/null
+++ b/dom/media/eme/MediaKeyMessageEvent.h
@@ -0,0 +1,64 @@
+/* -*- 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 "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 {
+class ErrorResult;
+
+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 0000000000..79190be710
--- /dev/null
+++ b/dom/media/eme/MediaKeySession.cpp
@@ -0,0 +1,622 @@
+/* -*- 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 <ctime>
+#include <utility>
+
+#include "GMPUtils.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/EMEUtils.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/KeyIdsInitDataBinding.h"
+#include "mozilla/dom/MediaEncryptedEvent.h"
+#include "mozilla/dom/MediaKeyError.h"
+#include "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaKeyStatusMap.h"
+#include "mozilla/dom/MediaKeySystemAccess.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsPrintfCString.h"
+#include "psshparser/PsshParser.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaKeySession, DOMEventTargetHelper,
+ mMediaKeyError, mKeys, mKeyStatusMap,
+ mClosed)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(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(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, "MediaKeys.createSession"_ns);
+}
+
+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() {
+ EME_LOG("MediaKeySession[%p,'%s'] dtor", this,
+ NS_ConvertUTF16toUTF8(mSessionId).get());
+}
+
+MediaKeyError* MediaKeySession::GetError() const { return mMediaKeyError; }
+
+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 MediaKeySession_Binding::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;
+ {
+ auto caps = mKeys->GetCDMProxy()->Capabilites().Lock();
+ 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()));
+ for (const CDMCaps::KeyStatus& status : keyStatuses) {
+ message.Append(nsPrintfCString(
+ " (%s,%s)", ToHexString(status.mId).get(),
+ nsCString(MediaKeyStatusValues::GetString(status.mStatus)).get()));
+ }
+ message.AppendLiteral(" }");
+ // Use %s so we aren't exposing random strings to printf interpolation.
+ EME_LOG("%s", 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(UTF_8_ENCODING->DecodeWithBOMRemoval(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, "MediaKeySession.generateRequest"_ns));
+ 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->MaybeRejectWithInvalidStateError(
+ "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->MaybeRejectWithInvalidStateError(
+ "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->MaybeRejectWithTypeError(
+ "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->MaybeRejectWithTypeError(
+ "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->MaybeRejectWithNotSupportedError(
+ "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->MaybeRejectWithTypeError(
+ "initData sanitization failed in MediaKeySession.generateRequest()");
+ EME_LOG(
+ "MediaKeySession[%p,'%s'] GenerateRequest() initData sanitization "
+ "failed",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get());
+ return promise.forget();
+ }
+
+ // Let sanitized init data be a validated and sanitized version of init data.
+
+ // If sanitized init data is empty, reject promise with a NotSupportedError.
+
+ // Note: Remaining steps of generateRequest method continue in CDM.
+
+ // Convert initData to hex for easier logging.
+ // Note: CreateSession() std::move()s the data out of the array, so we have
+ // to copy it here.
+ nsAutoCString hexInitData(ToHexString(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='%s' initDataType='%s'",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid, hexInitData.get(),
+ NS_ConvertUTF16toUTF8(aInitDataType).get());
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise> MediaKeySession::Load(const nsAString& aSessionId,
+ ErrorResult& aRv) {
+ RefPtr<DetailedPromise> promise(MakePromise(aRv, "MediaKeySession.load"_ns));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // 1. If this object is closed, return a promise rejected with an
+ // InvalidStateError.
+ if (IsClosed()) {
+ promise->MaybeRejectWithInvalidStateError(
+ "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->MaybeRejectWithInvalidStateError(
+ "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->MaybeRejectWithTypeError(
+ "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->MaybeRejectWithTypeError(
+ "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, mSessionType, 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, "MediaKeySession.update"_ns));
+ 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->MaybeRejectWithInvalidStateError(
+ "MediaKeySession.Update() called before sessionId set by CDM");
+ return promise.forget();
+ }
+
+ nsTArray<uint8_t> data;
+ if (IsClosed() || !mKeys->GetCDMProxy()) {
+ promise->MaybeRejectWithInvalidStateError(
+ "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->MaybeRejectWithTypeError(
+ "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 hex for easier logging.
+ // Note: UpdateSession() std::move()s the data out of the array, so we have
+ // to copy it here.
+ nsAutoCString hexResponse(ToHexString(data));
+
+ PromiseId pid = mKeys->StorePromise(promise);
+ mKeys->GetCDMProxy()->UpdateSession(mSessionId, pid, data);
+
+ EME_LOG(
+ "MediaKeySession[%p,'%s'] Update() sent to CDM, "
+ "promiseId=%d Response='%s'",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid, hexResponse.get());
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise> MediaKeySession::Close(ErrorResult& aRv) {
+ RefPtr<DetailedPromise> promise(MakePromise(aRv, "MediaKeySession.close"_ns));
+ 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->MaybeRejectWithInvalidStateError(
+ "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->MaybeRejectWithInvalidStateError(
+ "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, "MediaKeySession.remove"_ns));
+ 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->MaybeRejectWithInvalidStateError(
+ "MediaKeySession.Remove() called before sessionId set by CDM");
+ return promise.forget();
+ }
+ if (mSessionType != MediaKeySessionType::Persistent_license) {
+ promise->MaybeRejectWithInvalidAccessError(
+ "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->MaybeRejectWithInvalidStateError(
+ "MediaKeySession.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='%s'",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(),
+ nsCString(MediaKeyMessageTypeValues::GetString(aMessageType)).get(),
+ ToHexString(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, u"keystatuseschange"_ns, CanBubble::eNo);
+ asyncDispatcher->PostDOMEvent();
+}
+
+uint32_t MediaKeySession::Token() const { return mToken; }
+
+already_AddRefed<DetailedPromise> MediaKeySession::MakePromise(
+ ErrorResult& aRv, const nsACString& aName) {
+ nsCOMPtr<nsIGlobalObject> global = 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(%.12lf) (%.2lf hours from now)",
+ this, NS_ConvertUTF16toUTF8(mSessionId).get(), aExpiration,
+ (aExpiration - 1000.0 * double(time(0))) / (1000.0 * 60 * 60));
+ mExpiration = aExpiration;
+}
+
+EventHandlerNonNull* MediaKeySession::GetOnkeystatuseschange() {
+ return GetEventHandler(nsGkAtoms::onkeystatuseschange);
+}
+
+void MediaKeySession::SetOnkeystatuseschange(EventHandlerNonNull* aCallback) {
+ SetEventHandler(nsGkAtoms::onkeystatuseschange, aCallback);
+}
+
+EventHandlerNonNull* MediaKeySession::GetOnmessage() {
+ return GetEventHandler(nsGkAtoms::onmessage);
+}
+
+void MediaKeySession::SetOnmessage(EventHandlerNonNull* aCallback) {
+ SetEventHandler(nsGkAtoms::onmessage, aCallback);
+}
+
+nsCString ToCString(MediaKeySessionType aType) {
+ return nsCString(MediaKeySessionTypeValues::GetString(aType));
+}
+
+nsString ToString(MediaKeySessionType aType) {
+ return NS_ConvertUTF8toUTF16(ToCString(aType));
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/eme/MediaKeySession.h b/dom/media/eme/MediaKeySession.h
new file mode 100644
index 0000000000..e19488c311
--- /dev/null
+++ b/dom/media/eme/MediaKeySession.h
@@ -0,0 +1,142 @@
+/* -*- 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 "DecoderDoctorLogger.h"
+#include "mozilla/Attributes.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/Mutex.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 {
+class ErrorResult;
+
+namespace dom {
+class MediaKeySession;
+} // namespace dom
+DDLoggedTypeName(dom::MediaKeySession);
+
+namespace dom {
+
+class ArrayBufferViewOrArrayBuffer;
+class MediaKeyError;
+class MediaKeyStatusMap;
+
+nsCString ToCString(MediaKeySessionType aType);
+
+nsString ToString(MediaKeySessionType aType);
+
+class MediaKeySession final : public DOMEventTargetHelper,
+ public DecoderDoctorLifeLogger<MediaKeySession> {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaKeySession,
+ DOMEventTargetHelper)
+ public:
+ MediaKeySession(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 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 0000000000..c53f516423
--- /dev/null
+++ b/dom/media/eme/MediaKeyStatusMap.cpp
@@ -0,0 +1,99 @@
+/* -*- 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"
+#include "GMPUtils.h"
+
+namespace mozilla::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() = default;
+
+JSObject* MediaKeyStatusMap::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MediaKeyStatusMap_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* MediaKeyStatusMap::GetParentObject() const {
+ return mParent;
+}
+
+void MediaKeyStatusMap::Get(const ArrayBufferViewOrArrayBuffer& aKey,
+ OwningMediaKeyStatusOrUndefined& aOutValue,
+ ErrorResult& aOutRv) const {
+ ArrayData keyId = GetArrayBufferViewOrArrayBufferData(aKey);
+ if (!keyId.IsValid()) {
+ aOutValue.SetUndefined();
+ return;
+ }
+ for (const KeyStatus& status : mStatuses) {
+ if (keyId == status.mKeyId) {
+ aOutValue.SetAsMediaKeyStatus() = status.mStatus;
+ 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);
+}
+
+nsString MediaKeyStatusMap::GetKeyIDAsHexString(uint32_t aIndex) const {
+ MOZ_ASSERT(aIndex < GetIterableLength());
+ return NS_ConvertUTF8toUTF16(ToHexString(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 mozilla::dom
diff --git a/dom/media/eme/MediaKeyStatusMap.h b/dom/media/eme/MediaKeyStatusMap.h
new file mode 100644
index 0000000000..35027b25e2
--- /dev/null
+++ b/dom/media/eme/MediaKeyStatusMap.h
@@ -0,0 +1,92 @@
+/* -*- 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/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 {
+class ErrorResult;
+
+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_WRAPPERCACHE_CLASS(MediaKeyStatusMap)
+
+ public:
+ explicit MediaKeyStatusMap(nsPIDOMWindowInner* aParent);
+
+ protected:
+ ~MediaKeyStatusMap();
+
+ public:
+ nsPIDOMWindowInner* GetParentObject() const;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void Get(const ArrayBufferViewOrArrayBuffer& aKey,
+ OwningMediaKeyStatusOrUndefined& 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;
+ nsString GetKeyIDAsHexString(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.Clone()), 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 0000000000..0d61226eb5
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -0,0 +1,1086 @@
+/* -*- 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 <functional>
+
+#include "DecoderDoctorDiagnostics.h"
+#include "DecoderTraits.h"
+#include "KeySystemConfig.h"
+#include "MediaContainerType.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/KeySystemNames.h"
+#include "mozilla/dom/MediaKeySystemAccessBinding.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/dom/MediaSource.h"
+#include "mozilla/EMEUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "nsDOMString.h"
+#include "nsIObserverService.h"
+#include "nsMimeTypes.h"
+#include "nsReadableUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsUnicharUtils.h"
+#include "WebMDecoder.h"
+
+#ifdef XP_WIN
+# include "WMFDecoderModule.h"
+#endif
+
+namespace mozilla::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
+
+static nsCString ToCString(const MediaKeySystemConfiguration& aConfig);
+
+MediaKeySystemAccess::MediaKeySystemAccess(
+ nsPIDOMWindowInner* aParent, const nsAString& aKeySystem,
+ const MediaKeySystemConfiguration& aConfig)
+ : mParent(aParent), mKeySystem(aKeySystem), mConfig(aConfig) {
+ EME_LOG("Created MediaKeySystemAccess for keysystem=%s config=%s",
+ NS_ConvertUTF16toUTF8(mKeySystem).get(),
+ mozilla::dom::ToCString(mConfig).get());
+}
+
+MediaKeySystemAccess::~MediaKeySystemAccess() = default;
+
+JSObject* MediaKeySystemAccess::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MediaKeySystemAccess_Binding::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 MediaKeySystemStatus EnsureCDMInstalled(const nsAString& aKeySystem,
+ nsACString& aOutMessage) {
+ if (!KeySystemConfig::Supports(aKeySystem)) {
+ aOutMessage = "CDM is not installed"_ns;
+ return MediaKeySystemStatus::Cdm_not_installed;
+ }
+
+ return MediaKeySystemStatus::Available;
+}
+
+/* static */
+MediaKeySystemStatus MediaKeySystemAccess::GetKeySystemStatus(
+ const nsAString& aKeySystem, nsACString& aOutMessage) {
+ MOZ_ASSERT(StaticPrefs::media_eme_enabled() ||
+ IsClearkeyKeySystem(aKeySystem));
+
+ if (IsClearkeyKeySystem(aKeySystem)) {
+ return EnsureCDMInstalled(aKeySystem, aOutMessage);
+ }
+
+ if (IsWidevineKeySystem(aKeySystem)) {
+ if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
+ if (!Preferences::GetBool("media.gmp-widevinecdm.enabled", false)) {
+ aOutMessage = "Widevine EME disabled"_ns;
+ 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 = nsLiteralCString(
+ "KeySystem or Minimum API level not met for Widevine EME");
+ return MediaKeySystemStatus::Cdm_not_supported;
+ }
+ return MediaKeySystemStatus::Available;
+#endif
+ }
+ }
+
+#ifdef MOZ_WMF_CDM
+ if (IsPlayReadyKeySystemAndSupported(aKeySystem) &&
+ KeySystemConfig::Supports(aKeySystem)) {
+ return MediaKeySystemStatus::Available;
+ }
+#endif
+
+ return MediaKeySystemStatus::Cdm_not_supported;
+}
+
+static KeySystemConfig::EMECodecString ToEMEAPICodecString(
+ const nsString& aCodec) {
+ if (IsAACCodecString(aCodec)) {
+ return KeySystemConfig::EME_CODEC_AAC;
+ }
+ if (aCodec.EqualsLiteral("opus")) {
+ return KeySystemConfig::EME_CODEC_OPUS;
+ }
+ if (aCodec.EqualsLiteral("vorbis")) {
+ return KeySystemConfig::EME_CODEC_VORBIS;
+ }
+ if (aCodec.EqualsLiteral("flac")) {
+ return KeySystemConfig::EME_CODEC_FLAC;
+ }
+ if (IsH264CodecString(aCodec)) {
+ return KeySystemConfig::EME_CODEC_H264;
+ }
+ if (IsVP8CodecString(aCodec)) {
+ return KeySystemConfig::EME_CODEC_VP8;
+ }
+ if (IsVP9CodecString(aCodec)) {
+ return KeySystemConfig::EME_CODEC_VP9;
+ }
+ return ""_ns;
+}
+
+static nsTArray<KeySystemConfig> GetSupportedKeySystems() {
+ nsTArray<KeySystemConfig> keySystemConfigs;
+
+ const nsTArray<nsString> keySystemNames{
+ NS_ConvertUTF8toUTF16(kClearKeyKeySystemName),
+ NS_ConvertUTF8toUTF16(kWidevineKeySystemName),
+#ifdef MOZ_WMF_CDM
+ NS_ConvertUTF8toUTF16(kPlayReadyKeySystemName),
+ NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware),
+#endif
+ };
+ for (const auto& name : keySystemNames) {
+ KeySystemConfig config;
+ if (KeySystemConfig::GetConfig(name, config)) {
+ if (IsClearkeyKeySystem(name) &&
+ StaticPrefs::media_clearkey_test_key_systems_enabled()) {
+ // Add testing key systems. These offer the same capabilities as the
+ // base clearkey system, so just clone clearkey and change the name.
+ KeySystemConfig clearkeyWithProtectionQuery{config};
+ clearkeyWithProtectionQuery.mKeySystem.AssignLiteral(
+ kClearKeyWithProtectionQueryKeySystemName);
+ keySystemConfigs.AppendElement(std::move(clearkeyWithProtectionQuery));
+ }
+ keySystemConfigs.AppendElement(std::move(config));
+ }
+ }
+
+ return keySystemConfigs;
+}
+
+static bool GetKeySystemConfig(const nsAString& aKeySystem,
+ KeySystemConfig& aOutKeySystemConfig) {
+ for (auto&& config : GetSupportedKeySystems()) {
+ if (config.mKeySystem.Equals(aKeySystem)) {
+ aOutKeySystemConfig = std::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 KeySystemConfig::ContainerSupport& aContainerSupport,
+ const nsTArray<KeySystemConfig::EMECodecString>& aCodecs,
+ DecoderDoctorDiagnostics* aDiagnostics) {
+ MOZ_ASSERT(aCodecType != Invalid);
+ for (const KeySystemConfig::EMECodecString& codec : aCodecs) {
+ MOZ_ASSERT(!codec.IsEmpty());
+
+ if (aContainerSupport.DecryptsAndDecodes(codec)) {
+ // GMP can decrypt-and-decode this codec.
+ continue;
+ }
+
+ if (aContainerSupport.Decrypts(codec)) {
+ IgnoredErrorResult rv;
+ MediaSource::IsTypeSupported(aContentType, aDiagnostics, rv);
+ if (!rv.Failed()) {
+ // 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 we assume Widevine
+ // will be used with AAC.
+ if (codec == KeySystemConfig::EME_CODEC_AAC &&
+ IsWidevineKeySystem(aKeySystem) &&
+ !WMFDecoderModule::CanCreateMFTDecoder(WMFStreamType::AAC)) {
+ if (aDiagnostics) {
+ aDiagnostics->SetKeySystemIssue(
+ DecoderDoctorDiagnostics::eWidevineWithNoWMF);
+ }
+ }
+#endif
+ return false;
+ }
+ return true;
+}
+
+// Returns if an encryption scheme is supported per:
+// https://github.com/WICG/encrypted-media-encryption-scheme/blob/master/explainer.md
+// To be supported the scheme should be one of:
+// - null
+// - missing (which will result in the nsString being set to void and thus null)
+// - one of the schemes supported by the CDM
+// If the pref to enable this behavior is not set, then the value should be
+// empty/null, as the dict member will not be exposed. In this case we will
+// always report support as we would before this feature was implemented.
+static bool SupportsEncryptionScheme(
+ const nsString& aEncryptionScheme,
+ const nsTArray<nsString>& aSupportedEncryptionSchemes) {
+ MOZ_ASSERT(
+ DOMStringIsNull(aEncryptionScheme) ||
+ StaticPrefs::media_eme_encrypted_media_encryption_scheme_enabled(),
+ "Encryption scheme checking support must be preffed on for "
+ "encryptionScheme to be a non-null string");
+ if (DOMStringIsNull(aEncryptionScheme)) {
+ // "A missing or null value indicates that any encryption scheme is
+ // acceptable."
+ return true;
+ }
+ return aSupportedEncryptionSchemes.Contains(aEncryptionScheme);
+}
+
+static bool ToSessionType(const nsAString& aSessionType,
+ MediaKeySessionType& aOutType) {
+ if (aSessionType.Equals(ToString(MediaKeySessionType::Temporary))) {
+ aOutType = MediaKeySessionType::Temporary;
+ return true;
+ }
+ if (aSessionType.Equals(ToString(MediaKeySessionType::Persistent_license))) {
+ aOutType = MediaKeySessionType::Persistent_license;
+ return true;
+ }
+ return false;
+}
+
+// 5.1.1 Is persistent session type?
+static bool IsPersistentSessionType(MediaKeySessionType aSessionType) {
+ return aSessionType == MediaKeySessionType::Persistent_license;
+}
+
+static bool ContainsSessionType(
+ const nsTArray<KeySystemConfig::SessionType>& aTypes,
+ const MediaKeySessionType& aSessionType) {
+ return (aSessionType == MediaKeySessionType::Persistent_license &&
+ aTypes.Contains(KeySystemConfig::SessionType::PersistentLicense)) ||
+ (aSessionType == MediaKeySessionType::Temporary &&
+ aTypes.Contains(KeySystemConfig::SessionType::Temporary));
+}
+
+CodecType GetMajorType(const MediaMIMEType& aMIMEType) {
+ if (aMIMEType.HasAudioMajorType()) {
+ return Audio;
+ }
+ if (aMIMEType.HasVideoMajorType()) {
+ return Video;
+ }
+ return Invalid;
+}
+
+static CodecType GetCodecType(const KeySystemConfig::EMECodecString& aCodec) {
+ if (aCodec.Equals(KeySystemConfig::EME_CODEC_AAC) ||
+ aCodec.Equals(KeySystemConfig::EME_CODEC_OPUS) ||
+ aCodec.Equals(KeySystemConfig::EME_CODEC_VORBIS) ||
+ aCodec.Equals(KeySystemConfig::EME_CODEC_FLAC)) {
+ return Audio;
+ }
+ if (aCodec.Equals(KeySystemConfig::EME_CODEC_H264) ||
+ aCodec.Equals(KeySystemConfig::EME_CODEC_VP8) ||
+ aCodec.Equals(KeySystemConfig::EME_CODEC_VP9)) {
+ return Video;
+ }
+ return Invalid;
+}
+
+static bool AllCodecsOfType(
+ const nsTArray<KeySystemConfig::EMECodecString>& aCodecs,
+ const CodecType aCodecType) {
+ for (const KeySystemConfig::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.1.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,
+ const std::function<void(const char*)>& aDeprecationLogFn) {
+ // 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& contentTypeString = capabilities.mContentType;
+ // Let robustness be requested media capability's robustness member.
+ const nsString& robustness = capabilities.mRobustness;
+ // Optional encryption scheme extension, see
+ // https://github.com/WICG/encrypted-media-encryption-scheme/blob/master/explainer.md
+ // This will only be exposed to JS if
+ // media.eme.encrypted-media-encryption-scheme.enabled is preffed on.
+ const nsString encryptionScheme = capabilities.mEncryptionScheme;
+ // If content type is the empty string, return null.
+ if (contentTypeString.IsEmpty()) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') rejected; "
+ "audio or video capability has empty contentType.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ return Sequence<MediaKeySystemMediaCapability>();
+ }
+ // If content type is an invalid or unrecognized MIME type, continue
+ // to the next iteration.
+ Maybe<MediaContainerType> maybeContainerType =
+ MakeMediaContainerType(contentTypeString);
+ if (!maybeContainerType) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "failed to parse contentTypeString as MIME type.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+ const MediaContainerType& containerType = *maybeContainerType;
+ bool invalid = false;
+ nsTArray<KeySystemConfig::EMECodecString> codecs;
+ for (const auto& codecString :
+ containerType.ExtendedType().Codecs().Range()) {
+ KeySystemConfig::EMECodecString emeCodec =
+ ToEMEAPICodecString(nsString(codecString));
+ if (emeCodec.IsEmpty()) {
+ invalid = true;
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "'%s' is an invalid codec string.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).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.)
+ const bool isMP4 =
+ DecoderTraits::IsMP4SupportedType(containerType, aDiagnostics);
+ if (isMP4 && !aKeySystem.mMP4.IsSupported()) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "MP4 requested but unsupported.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+ const bool isWebM = WebMDecoder::IsSupportedType(containerType);
+ if (isWebM && !aKeySystem.mWebM.IsSupported()) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s,'%s') unsupported; "
+ "WebM requested but unsupported.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+ if (!isMP4 && !isWebM) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "Unsupported or unrecognized container requested.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).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(contentTypeString)) {
+ 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()) {
+ // Log deprecation warning to encourage authors to not do this!
+ aDeprecationLogFn("MediaEMENoCodecsDeprecatedWarning");
+ // TODO: Remove this once we're sure it doesn't break the web.
+ // If container normatively implies a specific set of codecs and codec
+ // constraints: Let parameters be that set.
+ if (isMP4) {
+ if (aCodecType == Audio) {
+ codecs.AppendElement(KeySystemConfig::EME_CODEC_AAC);
+ } else if (aCodecType == Video) {
+ codecs.AppendElement(KeySystemConfig::EME_CODEC_H264);
+ }
+ } else if (isWebM) {
+ if (aCodecType == Audio) {
+ codecs.AppendElement(KeySystemConfig::EME_CODEC_VORBIS);
+ } else if (aCodecType == Video) {
+ codecs.AppendElement(KeySystemConfig::EME_CODEC_VP8);
+ }
+ }
+ // Otherwise: Continue to the next iteration.
+ // (Note: all containers we support have implied codecs, so don't continue
+ // here.)
+ }
+
+ // If container type is not strictly a audio/video type, continue to the
+ // next iteration.
+ const auto majorType = GetMajorType(containerType.Type());
+ if (majorType == Invalid) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "MIME type is not an audio or video MIME type.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+ if (majorType != aCodecType || !AllCodecsOfType(codecs, aCodecType)) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "MIME type mixes audio codecs in video capabilities "
+ "or video codecs in audio capabilities.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).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','%s') unsupported; "
+ "unsupported robustness string.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+ if (majorType == Video &&
+ !aKeySystem.mVideoRobustness.Contains(robustness)) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "unsupported robustness string.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+ // Note: specified robustness requirements are satisfied.
+ }
+
+ // If preffed on: "In the Get Supported Capabilities for Audio/Video Type
+ // algorithm, implementations must skip capabilities specifying unsupported
+ // encryption schemes."
+ if (!SupportsEncryptionScheme(encryptionScheme,
+ aKeySystem.mEncryptionSchemes)) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "encryption scheme unsupported by CDM requested.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+
+ // 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, contentTypeString,
+ majorType, containerSupport, codecs,
+ aDiagnostics)) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "codec unsupported by CDM requested.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).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 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 KeySystemConfig::Requirement aKeySystemRequirement,
+ 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 &&
+ aKeySystemRequirement == KeySystemConfig::Requirement::NotAllowed) {
+ 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 (aKeySystemRequirement == KeySystemConfig::Requirement::NotAllowed) {
+ 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 (aKeySystemRequirement == KeySystemConfig::Requirement::Required) {
+ return false;
+ }
+ break;
+ }
+ default: {
+ return false;
+ }
+ }
+
+ // Set the requirement member of accumulated configuration to equal
+ // calculated requirement.
+ aOutRequirement = requirement;
+
+ return true;
+}
+
+// 3.1.1.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 {
+ // Note: fallible. Results in an empty array.
+ (void)sessionTypes.AppendElement(ToString(MediaKeySessionType::Temporary),
+ mozilla::fallible);
+ }
+ return sessionTypes;
+}
+
+// 3.1.1.2 Get Supported Configuration and Consent
+static bool GetSupportedConfig(
+ const KeySystemConfig& aKeySystem,
+ const MediaKeySystemConfiguration& aCandidate,
+ MediaKeySystemConfiguration& aOutConfig,
+ DecoderDoctorDiagnostics* aDiagnostics, bool aInPrivateBrowsing,
+ const std::function<void(const char*)>& aDeprecationLogFn) {
+ // 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;
+ }
+
+ if (config.mPersistentState == MediaKeysRequirement::Required &&
+ aInPrivateBrowsing) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') rejected; "
+ "persistentState requested in Private Browsing window.",
+ 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 (!ContainsSessionType(aKeySystem.mSessionTypes, 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(std::move(sessionTypes));
+
+ // If the videoCapabilities and audioCapabilities members in candidate
+ // configuration are both empty, return NotSupported.
+ if (aCandidate.mAudioCapabilities.IsEmpty() &&
+ aCandidate.mVideoCapabilities.IsEmpty()) {
+ // 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.
+ // Log deprecation warning to encourage authors to not do this!
+ aDeprecationLogFn("MediaEMENoCapabilitiesDeprecatedWarning");
+ }
+
+ // 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, aDeprecationLogFn);
+ // 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 = std::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, aDeprecationLogFn);
+ // 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 = std::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 ==
+ KeySystemConfig::Requirement::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 == KeySystemConfig::Requirement::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::CanCreateMFTDecoder(WMFStreamType::AAC)) {
+ 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, bool aIsPrivateBrowsing,
+ const std::function<void(const char*)>& aDeprecationLogFn) {
+ KeySystemConfig implementation;
+ if (!GetKeySystemConfig(aKeySystem, implementation)) {
+ return false;
+ }
+ for (const MediaKeySystemConfiguration& candidate : aConfigs) {
+ if (mozilla::dom::GetSupportedConfig(implementation, candidate, aOutConfig,
+ aDiagnostics, aIsPrivateBrowsing,
+ aDeprecationLogFn)) {
+ 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::kMediaKeysRequestTopic,
+ json.get());
+ }
+}
+
+static nsCString ToCString(const nsString& aString) {
+ nsCString str("'");
+ str.Append(NS_ConvertUTF16toUTF8(aString));
+ str.AppendLiteral("'");
+ return str;
+}
+
+static nsCString ToCString(const MediaKeysRequirement aValue) {
+ nsCString str("'");
+ str.AppendASCII(MediaKeysRequirementValues::GetString(aValue));
+ str.AppendLiteral("'");
+ return str;
+}
+
+static nsCString ToCString(const MediaKeySystemMediaCapability& aValue) {
+ nsCString str;
+ str.AppendLiteral("{contentType=");
+ str.Append(ToCString(aValue.mContentType));
+ str.AppendLiteral(", robustness=");
+ str.Append(ToCString(aValue.mRobustness));
+ str.AppendLiteral(", encryptionScheme=");
+ str.Append(ToCString(aValue.mEncryptionScheme));
+ str.AppendLiteral("}");
+ return str;
+}
+
+template <class Type>
+static nsCString ToCString(const Sequence<Type>& aSequence) {
+ nsCString str;
+ str.AppendLiteral("[");
+ StringJoinAppend(str, ","_ns, aSequence,
+ [](nsACString& dest, const Type& element) {
+ dest.Append(ToCString(element));
+ });
+ str.AppendLiteral("]");
+ return str;
+}
+
+template <class Type>
+static nsCString ToCString(const Optional<Sequence<Type>>& aOptional) {
+ nsCString str;
+ if (aOptional.WasPassed()) {
+ str.Append(ToCString(aOptional.Value()));
+ } else {
+ str.AppendLiteral("[]");
+ }
+ return str;
+}
+
+static nsCString ToCString(const MediaKeySystemConfiguration& aConfig) {
+ nsCString str;
+ str.AppendLiteral("{label=");
+ str.Append(ToCString(aConfig.mLabel));
+
+ str.AppendLiteral(", initDataTypes=");
+ str.Append(ToCString(aConfig.mInitDataTypes));
+
+ str.AppendLiteral(", audioCapabilities=");
+ str.Append(ToCString(aConfig.mAudioCapabilities));
+
+ str.AppendLiteral(", videoCapabilities=");
+ str.Append(ToCString(aConfig.mVideoCapabilities));
+
+ str.AppendLiteral(", distinctiveIdentifier=");
+ str.Append(ToCString(aConfig.mDistinctiveIdentifier));
+
+ str.AppendLiteral(", persistentState=");
+ str.Append(ToCString(aConfig.mPersistentState));
+
+ str.AppendLiteral(", sessionTypes=");
+ str.Append(ToCString(aConfig.mSessionTypes));
+
+ str.AppendLiteral("}");
+
+ return str;
+}
+
+/* static */
+nsCString MediaKeySystemAccess::ToCString(
+ const Sequence<MediaKeySystemConfiguration>& aConfig) {
+ return mozilla::dom::ToCString(aConfig);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/eme/MediaKeySystemAccess.h b/dom/media/eme/MediaKeySystemAccess.h
new file mode 100644
index 0000000000..b7ad9086b1
--- /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 "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;
+class ErrorResult;
+
+namespace dom {
+
+class MediaKeySystemAccess final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_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 void NotifyObservers(nsPIDOMWindowInner* aWindow,
+ const nsAString& aKeySystem,
+ MediaKeySystemStatus aStatus);
+
+ static bool GetSupportedConfig(
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ MediaKeySystemConfiguration& aOutConfig,
+ DecoderDoctorDiagnostics* aDiagnostics, bool aIsPrivateBrowsing,
+ const std::function<void(const char*)>& aDeprecationLogFn);
+
+ static bool KeySystemSupportsInitDataType(const nsAString& aKeySystem,
+ const nsAString& aInitDataType);
+
+ static nsCString ToCString(
+ const Sequence<MediaKeySystemConfiguration>& aConfig);
+
+ 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 0000000000..e6febfef54
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccessManager.cpp
@@ -0,0 +1,684 @@
+/* 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 "MediaKeySystemAccessPermissionRequest.h"
+#include "VideoUtils.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/DetailedPromise.h"
+#include "mozilla/EMEUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/Unused.h"
+#ifdef XP_WIN
+# include "mozilla/WindowsVersion.h"
+#endif
+#ifdef XP_MACOSX
+# include "nsCocoaFeatures.h"
+#endif
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsTHashMap.h"
+#include "nsIObserverService.h"
+#include "nsIScriptError.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla::dom {
+
+MediaKeySystemAccessManager::PendingRequest::PendingRequest(
+ DetailedPromise* aPromise, const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs)
+ : mPromise(aPromise), mKeySystem(aKeySystem), mConfigs(aConfigs) {
+ MOZ_COUNT_CTOR(MediaKeySystemAccessManager::PendingRequest);
+}
+
+MediaKeySystemAccessManager::PendingRequest::~PendingRequest() {
+ MOZ_COUNT_DTOR(MediaKeySystemAccessManager::PendingRequest);
+}
+
+void MediaKeySystemAccessManager::PendingRequest::CancelTimer() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+void MediaKeySystemAccessManager::PendingRequest::
+ RejectPromiseWithInvalidAccessError(const nsACString& aReason) {
+ if (mPromise) {
+ mPromise->MaybeRejectWithInvalidAccessError(aReason);
+ }
+}
+
+void MediaKeySystemAccessManager::PendingRequest::
+ RejectPromiseWithNotSupportedError(const nsACString& aReason) {
+ if (mPromise) {
+ mPromise->MaybeRejectWithNotSupportedError(aReason);
+ }
+}
+
+void MediaKeySystemAccessManager::PendingRequest::RejectPromiseWithTypeError(
+ const nsACString& aReason) {
+ if (mPromise) {
+ mPromise->MaybeRejectWithTypeError(aReason);
+ }
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccessManager)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+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->mPendingInstallRequests.Length(); i++) {
+ tmp->mPendingInstallRequests[i]->CancelTimer();
+ tmp->mPendingInstallRequests[i]->RejectPromiseWithInvalidAccessError(
+ nsLiteralCString(
+ "Promise still outstanding at MediaKeySystemAccessManager GC"));
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingInstallRequests[i]->mPromise)
+ }
+ tmp->mPendingInstallRequests.Clear();
+ for (size_t i = 0; i < tmp->mPendingAppApprovalRequests.Length(); i++) {
+ tmp->mPendingAppApprovalRequests[i]->RejectPromiseWithInvalidAccessError(
+ nsLiteralCString(
+ "Promise still outstanding at MediaKeySystemAccessManager GC"));
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAppApprovalRequests[i]->mPromise)
+ }
+ tmp->mPendingAppApprovalRequests.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->mPendingInstallRequests.Length(); i++) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingInstallRequests[i]->mPromise)
+ }
+ for (size_t i = 0; i < tmp->mPendingAppApprovalRequests.Length(); i++) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAppApprovalRequests[i]->mPromise)
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+#define MKSAM_LOG_DEBUG(msg, ...) \
+ EME_LOG("MediaKeySystemAccessManager::%s " msg, __func__, ##__VA_ARGS__)
+
+MediaKeySystemAccessManager::MediaKeySystemAccessManager(
+ nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+MediaKeySystemAccessManager::~MediaKeySystemAccessManager() {
+ MOZ_ASSERT(NS_IsMainThread());
+ Shutdown();
+}
+
+void MediaKeySystemAccessManager::Request(
+ DetailedPromise* aPromise, const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs) {
+ MOZ_ASSERT(NS_IsMainThread());
+ CheckDoesWindowSupportProtectedMedia(
+ MakeUnique<PendingRequest>(aPromise, aKeySystem, aConfigs));
+}
+
+void MediaKeySystemAccessManager::CheckDoesWindowSupportProtectedMedia(
+ UniquePtr<PendingRequest> aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRequest);
+ MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s",
+ NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
+
+ // In Windows OS, some Firefox windows that host content cannot support
+ // protected content, so check the status of support for this window.
+ // On other platforms windows should always support protected media.
+#ifdef XP_WIN
+ RefPtr<BrowserChild> browser(BrowserChild::GetFrom(mWindow));
+ if (!browser) {
+ if (!XRE_IsParentProcess() || XRE_IsE10sParentProcess()) {
+ // In this case, there is no browser because the Navigator object has
+ // been disconnected from its window. Thus, reject the promise.
+ aRequest->mPromise->MaybeRejectWithTypeError(
+ "Browsing context is no longer available");
+ } else {
+ // In this case, there is no browser because e10s is off. Proceed with
+ // the request with support since this scenario is always supported.
+ MKSAM_LOG_DEBUG("Allowing protected media on Windows with e10s off.");
+
+ OnDoesWindowSupportProtectedMedia(true, std::move(aRequest));
+ }
+
+ return;
+ }
+
+ RefPtr<MediaKeySystemAccessManager> self(this);
+
+ MKSAM_LOG_DEBUG(
+ "Checking with browser if this window supports protected media.");
+ browser->DoesWindowSupportProtectedMedia()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self, request = std::move(aRequest)](
+ const BrowserChild::IsWindowSupportingProtectedMediaPromise::
+ ResolveOrRejectValue& value) mutable {
+ if (value.IsResolve()) {
+ self->OnDoesWindowSupportProtectedMedia(value.ResolveValue(),
+ std::move(request));
+ } else {
+ EME_LOG(
+ "MediaKeySystemAccessManager::DoesWindowSupportProtectedMedia-"
+ "ResolveOrRejectLambda Failed to make IPC call to "
+ "IsWindowSupportingProtectedMedia: "
+ "reason=%d",
+ static_cast<int>(value.RejectValue()));
+ // Treat as failure.
+ self->OnDoesWindowSupportProtectedMedia(false, std::move(request));
+ }
+ });
+
+#else
+ // Non-Windows OS windows always support protected media.
+ MKSAM_LOG_DEBUG(
+ "Allowing protected media because all non-Windows OS windows support "
+ "protected media.");
+ OnDoesWindowSupportProtectedMedia(true, std::move(aRequest));
+#endif
+}
+
+void MediaKeySystemAccessManager::OnDoesWindowSupportProtectedMedia(
+ bool aIsSupportedInWindow, UniquePtr<PendingRequest> aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRequest);
+ MKSAM_LOG_DEBUG("aIsSupportedInWindow=%s aRequest->mKeySystem=%s",
+ aIsSupportedInWindow ? "true" : "false",
+ NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
+
+ if (!aIsSupportedInWindow) {
+ aRequest->RejectPromiseWithNotSupportedError(
+ "EME is not supported in this window"_ns);
+ return;
+ }
+
+ RequestMediaKeySystemAccess(std::move(aRequest));
+}
+
+void MediaKeySystemAccessManager::CheckDoesAppAllowProtectedMedia(
+ UniquePtr<PendingRequest> aRequest) {
+ // At time of writing, only GeckoView is expected to leverage the need to
+ // approve EME requests from the application. However, this functionality
+ // can be tested on all platforms by manipulating the
+ // media.eme.require-app-approval + test prefs associated with
+ // MediaKeySystemPermissionRequest.
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRequest);
+ MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s",
+ NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
+
+ if (!StaticPrefs::media_eme_require_app_approval()) {
+ MKSAM_LOG_DEBUG(
+ "media.eme.require-app-approval is false, allowing request.");
+ // We don't require app approval as the pref is not set. Treat as app
+ // approving by passing true to the handler.
+ OnDoesAppAllowProtectedMedia(true, std::move(aRequest));
+ return;
+ }
+
+ if (mAppAllowsProtectedMediaPromiseRequest.Exists()) {
+ // We already have a pending permission request, we don't need to fire
+ // another. Just wait for the existing permission request to be handled
+ // and the result from that will be used to handle the current request.
+ MKSAM_LOG_DEBUG(
+ "mAppAllowsProtectedMediaPromiseRequest already exists. aRequest "
+ "addded to queue and will be handled when exising permission request "
+ "is serviced.");
+ mPendingAppApprovalRequests.AppendElement(std::move(aRequest));
+ return;
+ }
+
+ RefPtr<MediaKeySystemAccessPermissionRequest> appApprovalRequest =
+ MediaKeySystemAccessPermissionRequest::Create(mWindow);
+ if (!appApprovalRequest) {
+ MKSAM_LOG_DEBUG(
+ "Failed to create app approval request! Blocking eme request as "
+ "fallback.");
+ aRequest->RejectPromiseWithInvalidAccessError(nsLiteralCString(
+ "Failed to create approval request to send to app embedding Gecko."));
+ return;
+ }
+
+ // If we're not using testing prefs (which take precedence over cached
+ // values) and have a cached value, handle based on the cached value.
+ if (appApprovalRequest->CheckPromptPrefs() ==
+ MediaKeySystemAccessPermissionRequest::PromptResult::Pending &&
+ mAppAllowsProtectedMedia) {
+ MKSAM_LOG_DEBUG(
+ "Short circuiting based on mAppAllowsProtectedMedia cached value");
+ OnDoesAppAllowProtectedMedia(*mAppAllowsProtectedMedia,
+ std::move(aRequest));
+ return;
+ }
+
+ // Store the approval request, it will be handled when we get a response
+ // from the app.
+ mPendingAppApprovalRequests.AppendElement(std::move(aRequest));
+
+ RefPtr<MediaKeySystemAccessPermissionRequest::RequestPromise> p =
+ appApprovalRequest->GetPromise();
+ p->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // Allow callback
+ [this,
+ self = RefPtr<MediaKeySystemAccessManager>(this)](bool aRequestResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRequestResult, "Result should be true on allow callback!");
+ mAppAllowsProtectedMediaPromiseRequest.Complete();
+ // Cache result.
+ mAppAllowsProtectedMedia = Some(aRequestResult);
+ // For each pending request, handle it based on the app's response.
+ for (UniquePtr<PendingRequest>& approvalRequest :
+ mPendingAppApprovalRequests) {
+ OnDoesAppAllowProtectedMedia(*mAppAllowsProtectedMedia,
+ std::move(approvalRequest));
+ }
+ self->mPendingAppApprovalRequests.Clear();
+ },
+ // Cancel callback
+ [this,
+ self = RefPtr<MediaKeySystemAccessManager>(this)](bool aRequestResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!aRequestResult,
+ "Result should be false on cancel callback!");
+ mAppAllowsProtectedMediaPromiseRequest.Complete();
+ // Cache result.
+ mAppAllowsProtectedMedia = Some(aRequestResult);
+ // For each pending request, handle it based on the app's response.
+ for (UniquePtr<PendingRequest>& approvalRequest :
+ mPendingAppApprovalRequests) {
+ OnDoesAppAllowProtectedMedia(*mAppAllowsProtectedMedia,
+ std::move(approvalRequest));
+ }
+ self->mPendingAppApprovalRequests.Clear();
+ })
+ ->Track(mAppAllowsProtectedMediaPromiseRequest);
+
+ // Prefs not causing short circuit, no cached value, go ahead and request
+ // permission.
+ MKSAM_LOG_DEBUG("Dispatching async request for app approval");
+ if (NS_FAILED(appApprovalRequest->Start())) {
+ // This shouldn't happen unless we're shutting down or similar edge cases.
+ // If this is regularly being hit then something is wrong and should be
+ // investigated.
+ MKSAM_LOG_DEBUG(
+ "Failed to start app approval request! Eme approval will be left in "
+ "limbo!");
+ }
+}
+
+void MediaKeySystemAccessManager::OnDoesAppAllowProtectedMedia(
+ bool aIsAllowed, UniquePtr<PendingRequest> aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRequest);
+ MKSAM_LOG_DEBUG("aIsAllowed=%s aRequest->mKeySystem=%s",
+ aIsAllowed ? "true" : "false",
+ NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
+ if (!aIsAllowed) {
+ aRequest->RejectPromiseWithNotSupportedError(
+ nsLiteralCString("The application embedding this user agent has "
+ "blocked MediaKeySystemAccess"));
+ return;
+ }
+
+ ProvideAccess(std::move(aRequest));
+}
+
+void MediaKeySystemAccessManager::RequestMediaKeySystemAccess(
+ UniquePtr<PendingRequest> aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRequest);
+ MKSAM_LOG_DEBUG("aIsSupportedInWindow=%s",
+ NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
+
+ // 1. If keySystem is the empty string, return a promise rejected with a newly
+ // created TypeError.
+ if (aRequest->mKeySystem.IsEmpty()) {
+ aRequest->mPromise->MaybeRejectWithTypeError("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;
+ }
+ // 2. If supportedConfigurations is empty, return a promise rejected with a
+ // newly created TypeError.
+ if (aRequest->mConfigs.IsEmpty()) {
+ aRequest->mPromise->MaybeRejectWithTypeError(
+ "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;
+ }
+
+ // 3. Let document be the calling context's Document.
+ // 4. Let origin be the origin of document.
+ // 5. Let promise be a new promise.
+ // 6. Run the following steps in parallel:
+
+ DecoderDoctorDiagnostics diagnostics;
+
+ // 1. If keySystem is not one of the Key Systems supported by the user
+ // agent, reject promise with a NotSupportedError. String comparison is
+ // case-sensitive.
+ if (!IsWidevineKeySystem(aRequest->mKeySystem) &&
+#ifdef MOZ_WMF_CDM
+ !IsPlayReadyKeySystemAndSupported(aRequest->mKeySystem) &&
+#endif
+ !IsClearkeyKeySystem(aRequest->mKeySystem)) {
+ // Not to inform user, because nothing to do if the keySystem is not
+ // supported.
+ aRequest->RejectPromiseWithNotSupportedError(
+ "Key system is unsupported"_ns);
+ diagnostics.StoreMediaKeySystemAccess(
+ mWindow->GetExtantDoc(), aRequest->mKeySystem, false, __func__);
+ return;
+ }
+
+ if (!StaticPrefs::media_eme_enabled() &&
+ !IsClearkeyKeySystem(aRequest->mKeySystem)) {
+ // 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.
+ // We don't show the notification if the pref is locked.
+ if (!Preferences::IsLocked("media.eme.enabled")) {
+ MediaKeySystemAccess::NotifyObservers(mWindow, aRequest->mKeySystem,
+ MediaKeySystemStatus::Api_disabled);
+ }
+ aRequest->RejectPromiseWithNotSupportedError("EME has been preffed off"_ns);
+ diagnostics.StoreMediaKeySystemAccess(
+ mWindow->GetExtantDoc(), aRequest->mKeySystem, false, __func__);
+ return;
+ }
+
+ nsAutoCString message;
+ MediaKeySystemStatus status =
+ MediaKeySystemAccess::GetKeySystemStatus(aRequest->mKeySystem, message);
+
+ nsPrintfCString msg(
+ "MediaKeySystemAccess::GetKeySystemStatus(%s) "
+ "result=%s msg='%s'",
+ NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get(),
+ nsCString(MediaKeySystemStatusValues::GetString(status)).get(),
+ message.get());
+ LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg));
+
+ // We may need to install the CDM to continue.
+ if (status == MediaKeySystemStatus::Cdm_not_installed &&
+ IsWidevineKeySystem(aRequest->mKeySystem)) {
+ // 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 (aRequest->mRequestType != PendingRequest::RequestType::Initial) {
+ MOZ_ASSERT(aRequest->mRequestType ==
+ PendingRequest::RequestType::Subsequent);
+ // CDM is not installed, but this is a subsequent request. We've waited,
+ // but can't service this request! Give up. Chrome will still be showing a
+ // "I can't play, updating" notification.
+ aRequest->RejectPromiseWithNotSupportedError(
+ "Timed out while waiting for a CDM update"_ns);
+ diagnostics.StoreMediaKeySystemAccess(
+ mWindow->GetExtantDoc(), aRequest->mKeySystem, false, __func__);
+ return;
+ }
+
+ const nsString keySystem = aRequest->mKeySystem;
+ if (AwaitInstall(std::move(aRequest))) {
+ // Notify chrome that we're going to wait for the CDM to download/update.
+ MediaKeySystemAccess::NotifyObservers(mWindow, keySystem, status);
+ } else {
+ // Failed to await the install. Log failure and give up trying to service
+ // this request.
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(), keySystem,
+ 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, aRequest->mKeySystem,
+ status);
+ aRequest->RejectPromiseWithNotSupportedError(message);
+ return;
+ }
+
+ nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+ nsTHashMap<nsCharPtrHashKey, bool> warnings;
+ std::function<void(const char*)> deprecationWarningLogFn =
+ [&](const char* aMsgName) {
+ EME_LOG(
+ "MediaKeySystemAccessManager::DeprecationWarningLambda Logging "
+ "deprecation warning '%s' to WebConsole.",
+ aMsgName);
+ warnings.InsertOrUpdate(aMsgName, true);
+ AutoTArray<nsString, 1> params;
+ nsString& uri = *params.AppendElement();
+ if (doc) {
+ Unused << doc->GetDocumentURI(uri);
+ }
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Media"_ns,
+ doc, nsContentUtils::eDOM_PROPERTIES,
+ aMsgName, params);
+ };
+
+ bool isPrivateBrowsing =
+ mWindow->GetExtantDoc() &&
+ mWindow->GetExtantDoc()->NodePrincipal()->GetPrivateBrowsingId() > 0;
+ // 2. Let implementation be the implementation of keySystem.
+ // 3. For each value in supportedConfigurations:
+ // 1. Let candidate configuration be the value.
+ // 2. Let supported configuration be the result of executing the Get
+ // Supported Configuration algorithm on implementation, candidate
+ // configuration, and origin.
+ // 3. If supported configuration is not NotSupported, run the following
+ // steps:
+ // 1. Let access be a new MediaKeySystemAccess object, and initialize it
+ // as follows:
+ // 1. Set the keySystem attribute to keySystem.
+ // 2. Let the configuration value be supported configuration.
+ // 3. Let the cdm implementation value be implementation.
+ // 2. Resolve promise with access and abort the parallel steps of this
+ // algorithm.
+ MediaKeySystemConfiguration config;
+ if (MediaKeySystemAccess::GetSupportedConfig(
+ aRequest->mKeySystem, aRequest->mConfigs, config, &diagnostics,
+ isPrivateBrowsing, deprecationWarningLogFn)) {
+ aRequest->mSupportedConfig = Some(config);
+ // The app gets the final say on if we provide access or not.
+ CheckDoesAppAllowProtectedMedia(std::move(aRequest));
+ return;
+ }
+ // 4. Reject promise with a NotSupportedError.
+
+ // Not to inform user, because nothing to do if the corresponding keySystem
+ // configuration is not supported.
+ aRequest->RejectPromiseWithNotSupportedError(
+ "Key system configuration is not supported"_ns);
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aRequest->mKeySystem, false, __func__);
+}
+
+void MediaKeySystemAccessManager::ProvideAccess(
+ UniquePtr<PendingRequest> aRequest) {
+ MOZ_ASSERT(aRequest);
+ MOZ_ASSERT(
+ aRequest->mSupportedConfig,
+ "The request needs a supported config if we're going to provide access!");
+ MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s",
+ NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
+
+ DecoderDoctorDiagnostics diagnostics;
+
+ RefPtr<MediaKeySystemAccess> access(new MediaKeySystemAccess(
+ mWindow, aRequest->mKeySystem, aRequest->mSupportedConfig.ref()));
+ aRequest->mPromise->MaybeResolve(access);
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aRequest->mKeySystem, true, __func__);
+}
+
+bool MediaKeySystemAccessManager::AwaitInstall(
+ UniquePtr<PendingRequest> aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRequest);
+ MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s",
+ NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
+
+ if (!EnsureObserversAdded()) {
+ NS_WARNING("Failed to add pref observer");
+ aRequest->RejectPromiseWithNotSupportedError(nsLiteralCString(
+ "Failed trying to setup CDM update: failed adding observers"));
+ return false;
+ }
+
+ nsCOMPtr<nsITimer> timer;
+ NS_NewTimerWithObserver(getter_AddRefs(timer), this, 60 * 1000,
+ nsITimer::TYPE_ONE_SHOT);
+ if (!timer) {
+ NS_WARNING("Failed to create timer to await CDM install.");
+ aRequest->RejectPromiseWithNotSupportedError(nsLiteralCString(
+ "Failed trying to setup CDM update: failed timer creation"));
+ return false;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ aRequest->mTimer == nullptr,
+ "Timer should not already be set on a request we're about to await");
+ aRequest->mTimer = timer;
+
+ mPendingInstallRequests.AppendElement(std::move(aRequest));
+ return true;
+}
+
+void MediaKeySystemAccessManager::RetryRequest(
+ UniquePtr<PendingRequest> aRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aRequest);
+ MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s",
+ NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get());
+ // Cancel and null timer if it exists.
+ aRequest->CancelTimer();
+ // Indicate that this is a request that's being retried.
+ aRequest->mRequestType = PendingRequest::RequestType::Subsequent;
+ RequestMediaKeySystemAccess(std::move(aRequest));
+}
+
+nsresult MediaKeySystemAccessManager::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MKSAM_LOG_DEBUG("%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<UniquePtr<PendingRequest>> requests;
+ for (size_t i = mPendingInstallRequests.Length(); i-- > 0;) {
+ nsAutoCString message;
+ MediaKeySystemStatus status = MediaKeySystemAccess::GetKeySystemStatus(
+ mPendingInstallRequests[i]->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(std::move(mPendingInstallRequests[i]));
+ mPendingInstallRequests.RemoveElementAt(i);
+ }
+ // Retry all pending requests, but this time fail if the CDM is not
+ // installed.
+ for (size_t i = requests.Length(); i-- > 0;) {
+ RetryRequest(std::move(requests[i]));
+ }
+ } 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 < mPendingInstallRequests.Length(); i++) {
+ if (mPendingInstallRequests[i]->mTimer == timer) {
+ EME_LOG("MediaKeySystemAccessManager::AwaitInstall resuming request");
+ UniquePtr<PendingRequest> request =
+ std::move(mPendingInstallRequests[i]);
+ mPendingInstallRequests.RemoveElementAt(i);
+ RetryRequest(std::move(request));
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult MediaKeySystemAccessManager::GetName(nsACString& aName) {
+ aName.AssignLiteral("MediaKeySystemAccessManager");
+ return NS_OK;
+}
+
+bool MediaKeySystemAccessManager::EnsureObserversAdded() {
+ MOZ_ASSERT(NS_IsMainThread());
+ 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() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MKSAM_LOG_DEBUG("");
+ for (const UniquePtr<PendingRequest>& installRequest :
+ mPendingInstallRequests) {
+ // Cancel all requests; we're shutting down.
+ installRequest->CancelTimer();
+ installRequest->RejectPromiseWithInvalidAccessError(nsLiteralCString(
+ "Promise still outstanding at MediaKeySystemAccessManager shutdown"));
+ }
+ mPendingInstallRequests.Clear();
+ for (const UniquePtr<PendingRequest>& approvalRequest :
+ mPendingAppApprovalRequests) {
+ approvalRequest->RejectPromiseWithInvalidAccessError(nsLiteralCString(
+ "Promise still outstanding at MediaKeySystemAccessManager shutdown"));
+ }
+ mPendingAppApprovalRequests.Clear();
+ mAppAllowsProtectedMediaPromiseRequest.DisconnectIfExists();
+ if (mAddedObservers) {
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->RemoveObserver(this, "gmp-changed");
+ mAddedObservers = false;
+ }
+ }
+}
+
+} // namespace mozilla::dom
+
+#undef MKSAM_LOG_DEBUG
diff --git a/dom/media/eme/MediaKeySystemAccessManager.h b/dom/media/eme/MediaKeySystemAccessManager.h
new file mode 100644
index 0000000000..992de2a9e7
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccessManager.h
@@ -0,0 +1,229 @@
+/* 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 DOM_MEDIA_MEDIAKEYSYSTEMACCESSMANAGER_H_
+#define DOM_MEDIA_MEDIAKEYSYSTEMACCESSMANAGER_H_
+
+#include "mozilla/dom/MediaKeySystemAccess.h"
+#include "mozilla/MozPromise.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIObserver.h"
+#include "nsISupportsImpl.h"
+#include "nsITimer.h"
+
+namespace mozilla::dom {
+
+class DetailedPromise;
+class TestGMPVideoDecoder;
+
+/**
+ * MediaKeySystemAccessManager implements the functionality for
+ * Navigator.requestMediaKeySystemAccess(). The navigator may perform its own
+ * logic before passing the request to this class, but the majority of
+ * processing happens the MediaKeySystemAccessManager. The manager is expected
+ * to be run entirely on the main thread of the content process for whichever
+ * window it is associated with.
+ *
+ * As well as implementing the Navigator.requestMediaKeySystemAccess()
+ * algorithm, the manager performs Gecko specific logic. For example, the EME
+ * specification does not specify a process to check if a CDM is installed as
+ * part of requesting access, but that is an important part of obtaining access
+ * for Gecko, and is handled by the manager.
+ *
+ * A request made to the manager can be thought of as entering a pipeline.
+ * In this pipeline the request must pass through various stages that can
+ * reject the request and remove it from the pipeline. If a request is not
+ * rejected by the end of the pipeline it is approved/resolved.
+ *
+ * The pipeline is structured in such a way that each step should be executed
+ * even if it will quickly be exited. For example, the step that checks if a
+ * window supports protected media is an instant approve on non-Windows OSes,
+ * but we want to execute the function representing that step to ensure a
+ * deterministic execution and logging path. The hope is this reduces
+ * complexity for when we need to debug or change the code.
+ *
+ * While the pipeline metaphor generally holds, the implementation details of
+ * the manager mean that processing is not always linear: a request may be
+ * re-injected earlier into the pipeline for reprocessing. This can happen
+ * if the request was pending some other operation, e.g. CDM install, after
+ * which we wish to reprocess that request. However, we strive to keep it
+ * as linear as possible.
+ *
+ * A high level version of the happy path pipeline is depicted below. If a
+ * request were to fail any of the steps below it would be rejected and ejected
+ * from the pipeline.
+ *
+ * Request arrives from navigator
+ * +
+ * |
+ * v
+ * Check if window supports protected media
+ * +
+ * +<-------------------+
+ * v |
+ * Check request args are sane |
+ * + |
+ * | Wait for CDM and retry
+ * v |
+ * Check if CDM is installed |
+ * + |
+ * | |
+ * +--------------------+
+ * |
+ * v
+ * Check if CDM supports args
+ * +
+ * |
+ * v
+ * Check if app allows protected media
+ * (used by GeckoView)
+ * +
+ * |
+ * v
+ * Provide access
+ *
+ */
+
+class MediaKeySystemAccessManager final : public nsIObserver, public nsINamed {
+ public:
+ explicit MediaKeySystemAccessManager(nsPIDOMWindowInner* aWindow);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(MediaKeySystemAccessManager,
+ nsIObserver)
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINAMED
+
+ // Entry point for the navigator to call into the manager.
+ void Request(DetailedPromise* aPromise, const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfig);
+
+ void Shutdown();
+
+ private:
+ // Encapsulates the information for a Navigator.requestMediaKeySystemAccess()
+ // request that is being processed.
+ struct PendingRequest {
+ enum class RequestType { Initial, Subsequent };
+
+ PendingRequest(DetailedPromise* aPromise, const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs);
+ ~PendingRequest();
+
+ // The JS promise associated with this request.
+ RefPtr<DetailedPromise> mPromise;
+ // The KeySystem passed for this request.
+ const nsString mKeySystem;
+ // The config(s) passed for this request.
+ const Sequence<MediaKeySystemConfiguration> mConfigs;
+
+ // If the request is
+ // - A first attempt request from JS: RequestType::Initial.
+ // - A request we're reprocessing due to a GMP being installed:
+ // RequestType::Subsequent.
+ RequestType mRequestType = RequestType::Initial;
+
+ // If we find a supported config for this request during processing it
+ // should be stored here. Only if we have a supported config should a
+ // request have access provided.
+ Maybe<MediaKeySystemConfiguration> mSupportedConfig;
+
+ // Will be set to trigger a timeout and re-processing of the request if the
+ // request is pending on some potentially time consuming operation, e.g.
+ // CDM install.
+ nsCOMPtr<nsITimer> mTimer = nullptr;
+
+ // Convenience methods to reject the wrapped promise.
+ void RejectPromiseWithInvalidAccessError(const nsACString& aReason);
+ void RejectPromiseWithNotSupportedError(const nsACString& aReason);
+ void RejectPromiseWithTypeError(const nsACString& aReason);
+
+ void CancelTimer();
+ };
+
+ // Check if the application (e.g. a GeckoView app) allows protected media in
+ // this window.
+ //
+ // This function is always expected to be executed as part of the pipeline of
+ // processing a request, but its behavior differs depending on prefs set.
+ //
+ // If the `media_eme_require_app_approval` pref is false, then the function
+ // assumes app approval and early returns. Otherwise the function will
+ // create a permission request to be approved by the embedding app. If the
+ // test prefs detailed in MediaKeySystemAccessPermissionRequest.h are set
+ // then they will control handling, otherwise it is up to the embedding
+ // app to handle the request.
+ //
+ // At the time of writing, only GeckoView based apps are expected to pref
+ // on this behavior.
+ //
+ // This function is expected to run late/last in the pipeline so that if we
+ // ask the app for permission we don't fail after the app okays the request.
+ // This is to avoid cases where a user may be prompted by the app to approve
+ // eme, this check then passes, but we fail later in the pipeline, leaving
+ // the user wondering why their approval didn't work.
+ void CheckDoesAppAllowProtectedMedia(UniquePtr<PendingRequest> aRequest);
+
+ // Handles the result of the app allowing or disallowing protected media.
+ // If there are pending requests in mPendingAppApprovalRequests then this
+ // needs to be called on each.
+ void OnDoesAppAllowProtectedMedia(bool aIsAllowed,
+ UniquePtr<PendingRequest> aRequest);
+
+ // Checks if the Window associated with this manager supports protected media
+ // and calls into OnDoesWindowSupportEncryptedMedia with the result.
+ void CheckDoesWindowSupportProtectedMedia(UniquePtr<PendingRequest> aRequest);
+
+ // Handle the result of checking if the window associated with this manager
+ // supports protected media. If the window doesn't support protected media
+ // this will reject the request, otherwise the request will continue to be
+ // processed.
+ void OnDoesWindowSupportProtectedMedia(bool aIsSupportedInWindow,
+ UniquePtr<PendingRequest> aRequest);
+
+ // Performs the 'requestMediaKeySystemAccess' algorithm detailed in the EME
+ // specification. Gecko may need to install a CDM to satisfy this check. If
+ // CDM install is needed this function may be called again for the same
+ // request once the CDM is installed or a timeout is reached.
+ void RequestMediaKeySystemAccess(UniquePtr<PendingRequest> aRequest);
+
+ // Approves aRequest and provides MediaKeySystemAccess by resolving the
+ // promise associated with the request.
+ void ProvideAccess(UniquePtr<PendingRequest> aRequest);
+
+ ~MediaKeySystemAccessManager();
+
+ bool EnsureObserversAdded();
+
+ bool AwaitInstall(UniquePtr<PendingRequest> aRequest);
+
+ void RetryRequest(UniquePtr<PendingRequest> aRequest);
+
+ // Requests waiting on approval from the application to be processed.
+ nsTArray<UniquePtr<PendingRequest>> mPendingAppApprovalRequests;
+
+ // Requests waiting on CDM installation to be processed.
+ nsTArray<UniquePtr<PendingRequest>> mPendingInstallRequests;
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ bool mAddedObservers = false;
+
+ // Has the app approved protected media playback? If it has we cache the
+ // value so we don't need to check again.
+ Maybe<bool> mAppAllowsProtectedMedia;
+
+ // If we're waiting for permission from the app to enable EME this holder
+ // should contain the request.
+ //
+ // Note the type in the holder should match
+ // MediaKeySystemAccessPermissionRequest::RequestPromise, but we can't
+ // include MediaKeySystemAccessPermissionRequest's header here without
+ // breaking the build, so we do this hack.
+ MozPromiseRequestHolder<MozPromise<bool, bool, true>>
+ mAppAllowsProtectedMediaPromiseRequest;
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_MEDIA_MEDIAKEYSYSTEMACCESSMANAGER_H_
diff --git a/dom/media/eme/MediaKeySystemAccessPermissionRequest.cpp b/dom/media/eme/MediaKeySystemAccessPermissionRequest.cpp
new file mode 100644
index 0000000000..063bf93f7e
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccessPermissionRequest.cpp
@@ -0,0 +1,91 @@
+/* -*- 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 "MediaKeySystemAccessPermissionRequest.h"
+
+#include "nsGlobalWindowInner.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaKeySystemAccessPermissionRequest,
+ ContentPermissionRequestBase)
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(
+ MediaKeySystemAccessPermissionRequest, ContentPermissionRequestBase)
+
+/* static */
+already_AddRefed<MediaKeySystemAccessPermissionRequest>
+MediaKeySystemAccessPermissionRequest::Create(nsPIDOMWindowInner* aWindow) {
+ // Could conceivably be created off main thread then used on main thread
+ // later. If we really need to do that at some point we could relax this
+ // assert.
+ AssertIsOnMainThread();
+ if (!aWindow) {
+ return nullptr;
+ }
+
+ nsGlobalWindowInner* innerWindow = nsGlobalWindowInner::Cast(aWindow);
+ if (!innerWindow->GetPrincipal()) {
+ return nullptr;
+ }
+
+ RefPtr<MediaKeySystemAccessPermissionRequest> request =
+ new MediaKeySystemAccessPermissionRequest(innerWindow);
+ return request.forget();
+}
+
+MediaKeySystemAccessPermissionRequest::MediaKeySystemAccessPermissionRequest(
+ nsGlobalWindowInner* aWindow)
+ : ContentPermissionRequestBase(aWindow->GetPrincipal(), aWindow,
+ "media.eme.require-app-approval"_ns,
+ "media-key-system-access"_ns) {}
+
+MediaKeySystemAccessPermissionRequest::
+ ~MediaKeySystemAccessPermissionRequest() {
+ AssertIsOnMainThread();
+ // If a request has not been serviced by the time it is destroyed, treat it
+ // as if the request was denied.
+ Cancel();
+}
+
+already_AddRefed<MediaKeySystemAccessPermissionRequest::RequestPromise>
+MediaKeySystemAccessPermissionRequest::GetPromise() {
+ return mPromiseHolder.Ensure(__func__);
+}
+
+nsresult MediaKeySystemAccessPermissionRequest::Start() {
+ // Check test prefs to see if we should short circuit. We want to do this
+ // before checking the cached value so we can have pref changes take effect
+ // without refreshing the page.
+ MediaKeySystemAccessPermissionRequest::PromptResult promptResult =
+ CheckPromptPrefs();
+ if (promptResult ==
+ MediaKeySystemAccessPermissionRequest::PromptResult::Granted) {
+ return Allow(JS::UndefinedHandleValue);
+ }
+ if (promptResult ==
+ MediaKeySystemAccessPermissionRequest::PromptResult::Denied) {
+ return Cancel();
+ }
+
+ return nsContentPermissionUtils::AskPermission(this, mWindow);
+}
+
+NS_IMETHODIMP
+MediaKeySystemAccessPermissionRequest::Allow(JS::Handle<JS::Value> aChoices) {
+ AssertIsOnMainThread();
+ mPromiseHolder.ResolveIfExists(true, __func__);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MediaKeySystemAccessPermissionRequest::Cancel() {
+ AssertIsOnMainThread();
+ mPromiseHolder.RejectIfExists(false, __func__);
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/eme/MediaKeySystemAccessPermissionRequest.h b/dom/media/eme/MediaKeySystemAccessPermissionRequest.h
new file mode 100644
index 0000000000..088cf50ab6
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccessPermissionRequest.h
@@ -0,0 +1,74 @@
+/* -*- 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 DOM_MEDIA_EME_MEDIAKEYSYSTEMACCESSPERMISSIONREQUEST_H_
+#define DOM_MEDIA_EME_MEDIAKEYSYSTEMACCESSPERMISSIONREQUEST_H_
+
+#include "mozilla/MozPromise.h"
+#include "nsContentPermissionHelper.h"
+
+class nsGlobalWindowInner;
+
+namespace mozilla::dom {
+
+/**
+ * This class encapsulates a permission request to allow media key system
+ * access. The intention is not for this class to be used in all cases of EME,
+ * but only when we need to seek explicit approval from an application using
+ * Gecko, such as an application embedding via GeckoView.
+ *
+ * media.eme.require-app-approval should be used to gate this functionality in
+ * gecko code, and is also used as the testing pref for
+ * ContentPermissionRequestBase. I.e. CheckPromptPrefs() will respond to having
+ * `media.eme.require-app-approval.prompt.testing` and
+ * `media.eme.require-app-approval.prompt.testing.allow` being set to true or
+ * false and will return an appropriate value to allow for test code to short
+ * circuit showing a prompt. Note that the code using this class needs to call
+ * CheckPromptPrefs and implement test specific logic, it is *not* handled by
+ * this class or ContentPermissionRequestBase.
+ *
+ * Expects to be used on main thread as ContentPermissionRequestBase uses
+ * PContentPermissionRequest which is managed by PContent which is main thread
+ * to main thread communication.
+ */
+class MediaKeySystemAccessPermissionRequest
+ : public ContentPermissionRequestBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
+ MediaKeySystemAccessPermissionRequest, ContentPermissionRequestBase)
+
+ using RequestPromise = MozPromise<bool, bool, true /* IsExclusive*/>;
+
+ // Create a MediaKeySystemAccessPermissionRequest.
+ // @param aWindow The window associated with this request.
+ static already_AddRefed<MediaKeySystemAccessPermissionRequest> Create(
+ nsPIDOMWindowInner* aWindow);
+
+ // Returns a promise that will be resolved if this request is allowed or
+ // rejected in the case the request is denied. If allowed the promise
+ // will resolve with true, otherwise it will resolve with false.
+ already_AddRefed<RequestPromise> GetPromise();
+
+ // Helper function that triggers the request. This function will check
+ // prefs and cancel or allow the request if the appropriate prefs are set,
+ // otherwise it will fire the request to the associated window.
+ nsresult Start();
+
+ // nsIContentPermissionRequest methods
+ NS_IMETHOD Cancel(void) override;
+ NS_IMETHOD Allow(JS::Handle<JS::Value> choices) override;
+
+ private:
+ explicit MediaKeySystemAccessPermissionRequest(nsGlobalWindowInner* aWindow);
+ ~MediaKeySystemAccessPermissionRequest();
+
+ MozPromiseHolder<RequestPromise> mPromiseHolder;
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_MEDIA_EME_MEDIAKEYSYSTEMACCESSPERMISSIONREQUEST_H_
diff --git a/dom/media/eme/MediaKeys.cpp b/dom/media/eme/MediaKeys.cpp
new file mode 100644
index 0000000000..0b256650a2
--- /dev/null
+++ b/dom/media/eme/MediaKeys.cpp
@@ -0,0 +1,844 @@
+/* -*- 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 "ChromiumCDMProxy.h"
+#include "GMPCrashHelper.h"
+#include "mozilla/EMEUtils.h"
+#include "mozilla/JSONStringWriteFuncs.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/MediaKeyError.h"
+#include "mozilla/dom/MediaKeyMessageEvent.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/dom/MediaKeyStatusMap.h"
+#include "mozilla/dom/MediaKeySystemAccess.h"
+#include "mozilla/dom/MediaKeysBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "nsContentCID.h"
+#include "nsContentTypeParser.h"
+#include "nsContentUtils.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "mozilla/MediaDrmCDMProxy.h"
+#endif
+#ifdef XP_WIN
+# include "mozilla/WindowsVersion.h"
+#endif
+#ifdef MOZ_WMF_CDM
+# include "mozilla/WMFCDMProxy.h"
+#endif
+
+namespace mozilla::dom {
+
+// We don't use NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE because we need to
+// disconnect our MediaKeys instances from the inner window (mparent) before
+// we unlink it.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(MediaKeys)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaKeys)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mKeySessions)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingSessions)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaKeys)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mElement)
+ tmp->DisconnectInnerWindow();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mKeySessions)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromises)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingSessions)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+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_ENTRY(nsIObserver)
+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() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ DisconnectInnerWindow();
+ Shutdown();
+ EME_LOG("MediaKeys[%p] destroyed", this);
+}
+
+NS_IMETHODIMP MediaKeys::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!strcmp(aTopic, kMediaKeysResponseTopic),
+ "Should only listen for responses to MediaKey requests");
+ EME_LOG("MediaKeys[%p] observing message with aTopic=%s aData=%s", this,
+ aTopic, NS_ConvertUTF16toUTF8(aData).get());
+ if (!strcmp(aTopic, kMediaKeysResponseTopic)) {
+ if (!mProxy) {
+ // This may happen if we're notified during shutdown or startup. If this
+ // is happening outside of those scenarios there's a bug.
+ EME_LOG(
+ "MediaKeys[%p] can't notify CDM of observed message as mProxy is "
+ "unset",
+ this);
+ return NS_OK;
+ }
+
+ if (u"capture-possible"_ns.Equals(aData)) {
+ mProxy->NotifyOutputProtectionStatus(
+ CDMProxy::OutputProtectionCheckStatus::CheckSuccessful,
+ CDMProxy::OutputProtectionCaptureStatus::CapturePossilbe);
+ } else if (u"capture-not-possible"_ns.Equals(aData)) {
+ mProxy->NotifyOutputProtectionStatus(
+ CDMProxy::OutputProtectionCheckStatus::CheckSuccessful,
+ CDMProxy::OutputProtectionCaptureStatus::CaptureNotPossible);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("No code paths should lead to the failure case");
+ // This should be unreachable, but gracefully handle in case.
+ mProxy->NotifyOutputProtectionStatus(
+ CDMProxy::OutputProtectionCheckStatus::CheckFailed,
+ CDMProxy::OutputProtectionCaptureStatus::Unused);
+ }
+ }
+ return NS_OK;
+}
+
+void MediaKeys::ConnectInnerWindow() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsPIDOMWindowInner> innerWindowParent = GetParentObject();
+ MOZ_ASSERT(innerWindowParent,
+ "We should only be connecting when we have an inner window!");
+ innerWindowParent->AddMediaKeysInstance(this);
+}
+
+void MediaKeys::DisconnectInnerWindow() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!GetParentObject()) {
+ // We don't have a parent. We've been cycle collected, or the window
+ // already notified us of its destruction and we cleared the ref.
+ return;
+ }
+
+ GetParentObject()->RemoveMediaKeysInstance(this);
+}
+
+void MediaKeys::OnInnerWindowDestroy() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ EME_LOG("MediaKeys[%p] OnInnerWindowDestroy()", this);
+
+ // The InnerWindow should clear its reference to this object after this call,
+ // so we don't need to explicitly call DisconnectInnerWindow before nulling.
+ mParent = nullptr;
+
+ // Don't call shutdown directly because (at time of writing) mProxy can
+ // spin the event loop when it's shutdown. This can change the world state
+ // in the middle of window destruction, which we do not want.
+ GetMainThreadSerialEventTarget()->Dispatch(
+ NewRunnableMethod("MediaKeys::Shutdown", this, &MediaKeys::Shutdown));
+}
+
+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 (const RefPtr<MediaKeySession>& session : mKeySessions.Values()) {
+ // XXX Could the RefPtr still be moved here?
+ keySessions.InsertOrUpdate(session->GetSessionId(), RefPtr{session});
+ }
+ for (const RefPtr<MediaKeySession>& session : keySessions.Values()) {
+ 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() {
+ EME_LOG("MediaKeys[%p]::Shutdown()", this);
+ if (mProxy) {
+ mProxy->Shutdown();
+ mProxy = nullptr;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService && mObserverAdded) {
+ observerService->RemoveObserver(this, kMediaKeysResponseTopic);
+ }
+
+ // Hold a self reference to keep us alive after we clear the self reference
+ // for each promise. This ensures we stay alive until we're done shutting
+ // down.
+ RefPtr<MediaKeys> selfReference = this;
+
+ for (const RefPtr<dom::DetailedPromise>& promise : mPromises.Values()) {
+ promise->MaybeRejectWithInvalidStateError(
+ "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 MediaKeys_Binding::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, "MediaKeys.setServerCertificate"_ns));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (!mProxy) {
+ NS_WARNING("Tried to use a MediaKeys without a CDM");
+ promise->MaybeRejectWithInvalidStateError(
+ "Null CDM in MediaKeys.setServerCertificate()");
+ return promise.forget();
+ }
+
+ nsTArray<uint8_t> data;
+ CopyArrayBufferViewOrArrayBufferData(aCert, data);
+ if (data.IsEmpty()) {
+ promise->MaybeRejectWithTypeError(
+ "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=%" PRIu32, this, id);
+
+ // Keep MediaKeys alive for the lifetime of its promises. Any still-pending
+ // promises are rejected in Shutdown().
+ EME_LOG("MediaKeys[%p]::StorePromise() calling AddRef()", this);
+ AddRef();
+
+#ifdef DEBUG
+ // We should not have already stored this promise!
+ for (const RefPtr<dom::DetailedPromise>& promise : mPromises.Values()) {
+ MOZ_ASSERT(promise != aPromise);
+ }
+#endif
+
+ mPromises.InsertOrUpdate(id, RefPtr{aPromise});
+ return id;
+}
+
+void MediaKeys::ConnectPendingPromiseIdWithToken(PromiseId aId,
+ uint32_t aToken) {
+ // Should only be called from MediaKeySession::GenerateRequest.
+ mPromiseIdToken.InsertOrUpdate(aId, aToken);
+ EME_LOG(
+ "MediaKeys[%p]::ConnectPendingPromiseIdWithToken() id=%u => token(%u)",
+ this, aId, aToken);
+}
+
+already_AddRefed<DetailedPromise> MediaKeys::RetrievePromise(PromiseId aId) {
+ EME_LOG("MediaKeys[%p]::RetrievePromise(aId=%" PRIu32 ")", this, aId);
+ if (!mPromises.Contains(aId)) {
+ EME_LOG("MediaKeys[%p]::RetrievePromise(aId=%" PRIu32
+ ") tried to retrieve non-existent promise!",
+ this, aId);
+ NS_WARNING(nsPrintfCString(
+ "Tried to retrieve a non-existent promise id=%" PRIu32, aId)
+ .get());
+ return nullptr;
+ }
+ RefPtr<DetailedPromise> promise;
+ mPromises.Remove(aId, getter_AddRefs(promise));
+ EME_LOG("MediaKeys[%p]::RetrievePromise(aId=%" PRIu32 ") calling Release()",
+ this, aId);
+ Release();
+ return promise.forget();
+}
+
+void MediaKeys::RejectPromise(PromiseId aId, ErrorResult&& aException,
+ const nsCString& aReason) {
+ uint32_t errorCodeAsInt = aException.ErrorCodeAsInt();
+ EME_LOG("MediaKeys[%p]::RejectPromise(%" PRIu32 ", 0x%" PRIx32 ")", this, aId,
+ errorCodeAsInt);
+
+ RefPtr<DetailedPromise> promise(RetrievePromise(aId));
+ if (!promise) {
+ EME_LOG("MediaKeys[%p]::RejectPromise(%" PRIu32 ", 0x%" PRIx32
+ ") couldn't retrieve promise! Bailing!",
+ this, aId, errorCodeAsInt);
+ 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(aException.Failed());
+ promise->MaybeReject(std::move(aException), aReason);
+
+ if (mCreatePromiseId == aId) {
+ // Note: This will probably destroy the MediaKeys object!
+ EME_LOG("MediaKeys[%p]::RejectPromise(%" PRIu32 ", 0x%" PRIx32
+ ") calling Release()",
+ this, aId, errorCodeAsInt);
+ 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.InsertOrUpdate(aSession->GetSessionId(), RefPtr{aSession});
+}
+
+void MediaKeys::ResolvePromise(PromiseId aId) {
+ EME_LOG("MediaKeys[%p]::ResolvePromise(%" PRIu32 ")", 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->MaybeRejectWithInvalidAccessError(
+ "CDM LoadSession() returned a different session ID than requested");
+ return;
+ }
+ mKeySessions.InsertOrUpdate(session->GetSessionId(), RefPtr{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() {
+ EME_LOG("MediaKeys[%p]::CreateCDMProxy()", this);
+ 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
+#ifdef MOZ_WMF_CDM
+ if (IsPlayReadyKeySystemAndSupported(mKeySystem)) {
+ proxy = new WMFCDMProxy(this, mKeySystem, mConfig);
+ } else
+#endif
+ {
+ proxy = new ChromiumCDMProxy(
+ this, mKeySystem, new MediaKeysGMPCrashHelper(this),
+ mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
+ mConfig.mPersistentState == MediaKeysRequirement::Required);
+ }
+ return proxy.forget();
+}
+
+already_AddRefed<DetailedPromise> MediaKeys::Init(ErrorResult& aRv) {
+ EME_LOG("MediaKeys[%p]::Init()", this);
+ RefPtr<DetailedPromise> promise(MakePromise(aRv, "MediaKeys::Init()"_ns));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Determine principal (at creation time) of the MediaKeys object.
+ nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetParentObject());
+ if (!sop) {
+ promise->MaybeRejectWithInvalidStateError(
+ "Couldn't get script principal in MediaKeys::Init");
+ return promise.forget();
+ }
+ mPrincipal = sop->GetPrincipal();
+
+ // Begin figuring out the top level principal.
+ nsCOMPtr<nsPIDOMWindowInner> window = GetParentObject();
+
+ // If we're in a top level document, getting the top level principal is easy.
+ // However, we're not in a top level doc this becomes more complicated. If
+ // we're not top level we need to get the top level principal, this can be
+ // done by reading the principal of the load info, which we can get of a
+ // document's channel.
+ //
+ // There is an edge case we need to watch out for here where this code can be
+ // run in an about:blank document before it has done its async load. In this
+ // case the document will not yet have a load info. We address this below by
+ // walking up a level in the window context chain. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1675360
+ // for more info.
+ Document* document = window->GetExtantDoc();
+ if (!document) {
+ NS_WARNING("Failed to get document when creating MediaKeys");
+ promise->MaybeRejectWithInvalidStateError(
+ "Couldn't get document in MediaKeys::Init");
+ return promise.forget();
+ }
+
+ WindowGlobalChild* windowGlobalChild = window->GetWindowGlobalChild();
+ if (!windowGlobalChild) {
+ NS_WARNING("Failed to get window global child when creating MediaKeys");
+ promise->MaybeRejectWithInvalidStateError(
+ "Couldn't get window global child in MediaKeys::Init");
+ return promise.forget();
+ }
+
+ if (windowGlobalChild->SameOriginWithTop()) {
+ // We're in the same origin as the top window context, so our principal
+ // is also the top principal.
+ mTopLevelPrincipal = mPrincipal;
+ } else {
+ // We have a different origin than the top doc, try and find the top level
+ // principal by looking it up via load info, which we read off a channel.
+ nsIChannel* channel = document->GetChannel();
+
+ WindowContext* windowContext = document->GetWindowContext();
+ if (!windowContext) {
+ NS_WARNING("Failed to get window context when creating MediaKeys");
+ promise->MaybeRejectWithInvalidStateError(
+ "Couldn't get window context in MediaKeys::Init");
+ return promise.forget();
+ }
+ while (!channel) {
+ // We don't have a channel, this can happen if we're in an about:blank
+ // page that hasn't yet had its async load performed. Try and get
+ // the channel from our parent doc. We should be able to do this because
+ // an about:blank is considered the same origin as its parent. We do this
+ // recursively to cover pages do silly things like nesting blank iframes
+ // and not waiting for loads.
+
+ // Move our window context up a level.
+ windowContext = windowContext->GetParentWindowContext();
+ if (!windowContext || !windowContext->GetExtantDoc()) {
+ NS_WARNING(
+ "Failed to get parent window context's document when creating "
+ "MediaKeys");
+ promise->MaybeRejectWithInvalidStateError(
+ "Couldn't get parent window context's document in "
+ "MediaKeys::Init (likely due to an nested about about:blank frame "
+ "that hasn't loaded yet)");
+ return promise.forget();
+ }
+
+ Document* parentDoc = windowContext->GetExtantDoc();
+ channel = parentDoc->GetChannel();
+ }
+
+ MOZ_RELEASE_ASSERT(
+ channel, "Should either have a channel or should have returned by now");
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ MOZ_RELEASE_ASSERT(loadInfo, "Channels should always have LoadInfo");
+ mTopLevelPrincipal = loadInfo->GetTopLevelPrincipal();
+ if (!mTopLevelPrincipal) {
+ NS_WARNING("Failed to get top level principal when creating MediaKeys");
+ promise->MaybeRejectWithInvalidStateError(
+ "Couldn't get top level principal in MediaKeys::Init");
+ return promise.forget();
+ }
+ }
+
+ // We should have figured out our top level principal.
+ if (!mPrincipal || !mTopLevelPrincipal) {
+ NS_WARNING("Failed to get principals when creating MediaKeys");
+ promise->MaybeRejectWithInvalidStateError(
+ "Couldn't get principal(s) in MediaKeys::Init");
+ return promise.forget();
+ }
+
+ nsAutoCString origin;
+ nsresult rv = mPrincipal->GetOrigin(origin);
+ if (NS_FAILED(rv)) {
+ promise->MaybeRejectWithInvalidStateError(
+ "Couldn't get principal origin string in MediaKeys::Init");
+ return promise.forget();
+ }
+ nsAutoCString topLevelOrigin;
+ rv = mTopLevelPrincipal->GetOrigin(topLevelOrigin);
+ if (NS_FAILED(rv)) {
+ promise->MaybeRejectWithInvalidStateError(
+ "Couldn't get top-level principal origin string in MediaKeys::Init");
+ return promise.forget();
+ }
+
+ EME_LOG("MediaKeys[%p]::Create() (%s, %s)", this, origin.get(),
+ topLevelOrigin.get());
+
+ mProxy = CreateCDMProxy();
+
+ // 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);
+ EME_LOG("MediaKeys[%p]::Init() calling AddRef()", this);
+ AddRef();
+ mProxy->Init(mCreatePromiseId, NS_ConvertUTF8toUTF16(origin),
+ NS_ConvertUTF8toUTF16(topLevelOrigin),
+ KeySystemToProxyName(mKeySystem));
+
+ ConnectInnerWindow();
+
+ return promise.forget();
+}
+
+void MediaKeys::OnCDMCreated(PromiseId aId, const uint32_t aPluginId) {
+ EME_LOG("MediaKeys[%p]::OnCDMCreated(aId=%" PRIu32 ", aPluginId=%" PRIu32 ")",
+ this, aId, aPluginId);
+ RefPtr<DetailedPromise> promise(RetrievePromise(aId));
+ if (!promise) {
+ return;
+ }
+ RefPtr<MediaKeys> keys(this);
+
+ promise->MaybeResolve(keys);
+ if (mCreatePromiseId == aId) {
+ EME_LOG("MediaKeys[%p]::OnCDMCreated(aId=%" PRIu32 ", aPluginId=%" PRIu32
+ ") calling Release()",
+ this, aId, aPluginId);
+ Release();
+ }
+
+ MediaKeySystemAccess::NotifyObservers(mParent, mKeySystem,
+ MediaKeySystemStatus::Cdm_created);
+}
+
+static bool IsSessionTypeSupported(const MediaKeySessionType aSessionType,
+ const MediaKeySystemConfiguration& aConfig) {
+ if (aSessionType == MediaKeySessionType::Temporary) {
+ // Temporary is always supported.
+ return true;
+ }
+ if (!aConfig.mSessionTypes.WasPassed()) {
+ // No other session types supported.
+ return false;
+ }
+ return aConfig.mSessionTypes.Value().Contains(ToString(aSessionType));
+}
+
+already_AddRefed<MediaKeySession> MediaKeys::CreateSession(
+ MediaKeySessionType aSessionType, ErrorResult& aRv) {
+ EME_LOG("MediaKeys[%p]::CreateSession(aSessionType=%" PRIu8 ")", this,
+ static_cast<uint8_t>(aSessionType));
+ if (!IsSessionTypeSupported(aSessionType, mConfig)) {
+ EME_LOG("MediaKeys[%p]::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(
+ GetParentObject(), this, mKeySystem, aSessionType, aRv);
+
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ DDLINKCHILD("session", session.get());
+
+ // Add session to the set of sessions awaiting their sessionId being ready.
+ EME_LOG("MediaKeys[%p]::CreateSession(aSessionType=%" PRIu8
+ ") putting session with token=%" PRIu32 " into mPendingSessions",
+ this, static_cast<uint8_t>(aSessionType), session->Token());
+ mPendingSessions.InsertOrUpdate(session->Token(), RefPtr{session});
+
+ return session.forget();
+}
+
+void MediaKeys::OnSessionLoaded(PromiseId aId, bool aSuccess) {
+ EME_LOG("MediaKeys[%p]::OnSessionLoaded() resolve promise id=%" PRIu32, this,
+ aId);
+
+ ResolvePromiseWithResult(aId, 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) {
+ EME_LOG("MediaKeys[%p]::GetPendingSession(aToken=%" PRIu32 ")", this, aToken);
+ RefPtr<MediaKeySession> session;
+ mPendingSessions.Get(aToken, getter_AddRefs(session));
+ mPendingSessions.Remove(aToken);
+ return session.forget();
+}
+
+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;
+}
+
+void MediaKeys::CheckIsElementCapturePossible() {
+ MOZ_ASSERT(NS_IsMainThread());
+ EME_LOG("MediaKeys[%p]::IsElementCapturePossible()", this);
+ // Note, HTMLMediaElement prevents capture of its content via Capture APIs
+ // on the element if it has a media keys attached (see bug 1071482). So we
+ // don't need to check those cases here (they are covered by tests).
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (!observerService) {
+ // This can happen if we're in shutdown which means we may be going away
+ // soon anyway, but respond saying capture is possible since we can't
+ // forward the check further.
+ if (mProxy) {
+ mProxy->NotifyOutputProtectionStatus(
+ CDMProxy::OutputProtectionCheckStatus::CheckFailed,
+ CDMProxy::OutputProtectionCaptureStatus::Unused);
+ }
+ return;
+ }
+ if (!mObserverAdded) {
+ nsresult rv =
+ observerService->AddObserver(this, kMediaKeysResponseTopic, false);
+ if (NS_FAILED(rv)) {
+ if (mProxy) {
+ mProxy->NotifyOutputProtectionStatus(
+ CDMProxy::OutputProtectionCheckStatus::CheckFailed,
+ CDMProxy::OutputProtectionCaptureStatus::Unused);
+ }
+ return;
+ }
+ mObserverAdded = true;
+ }
+
+ if (mCaptureCheckRequestJson.IsEmpty()) {
+ // Lazily populate the JSON the first time we need it.
+ JSONStringWriteFunc<nsAutoCString> json;
+ JSONWriter jw{json};
+ jw.Start();
+ jw.StringProperty("status", "is-capture-possible");
+ jw.StringProperty("keySystem", NS_ConvertUTF16toUTF8(mKeySystem));
+ jw.End();
+ mCaptureCheckRequestJson = NS_ConvertUTF8toUTF16(json.StringCRef());
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!mCaptureCheckRequestJson.IsEmpty());
+ observerService->NotifyObservers(mParent.get(), kMediaKeysRequestTopic,
+ mCaptureCheckRequestJson.get());
+}
+
+void MediaKeys::GetSessionsInfo(nsString& sessionsInfo) {
+ for (const auto& keySession : mKeySessions.Values()) {
+ nsString sessionID;
+ keySession->GetSessionId(sessionID);
+ sessionsInfo.AppendLiteral("(sid=");
+ sessionsInfo.Append(sessionID);
+ MediaKeyStatusMap* keyStatusMap = keySession->KeyStatuses();
+ for (uint32_t i = 0; i < keyStatusMap->GetIterableLength(); i++) {
+ nsString keyID = keyStatusMap->GetKeyIDAsHexString(i);
+ sessionsInfo.AppendLiteral("(kid=");
+ sessionsInfo.Append(keyID);
+ sessionsInfo.AppendLiteral(" status=");
+ sessionsInfo.AppendASCII(
+ MediaKeyStatusValues::GetString(keyStatusMap->GetValueAtIndex(i)));
+ sessionsInfo.AppendLiteral(")");
+ }
+ sessionsInfo.AppendLiteral(")");
+ }
+}
+
+already_AddRefed<Promise> MediaKeys::GetStatusForPolicy(
+ const MediaKeysPolicy& aPolicy, ErrorResult& aRv) {
+ RefPtr<DetailedPromise> promise(
+ MakePromise(aRv, "MediaKeys::GetStatusForPolicy()"_ns));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Currently, only widevine CDM supports for this API.
+ if (!IsWidevineKeySystem(mKeySystem)) {
+ EME_LOG(
+ "MediaKeys[%p]::GetStatusForPolicy() HDCP policy check on unsupported "
+ "keysystem ",
+ this);
+ NS_WARNING("Tried to query without a CDM");
+ promise->MaybeRejectWithNotSupportedError(
+ "HDCP policy check on unsupported keysystem");
+ return promise.forget();
+ }
+
+ if (!mProxy) {
+ NS_WARNING("Tried to use a MediaKeys without a CDM");
+ promise->MaybeRejectWithInvalidStateError(
+ "Null CDM in MediaKeys.GetStatusForPolicy()");
+ return promise.forget();
+ }
+
+ EME_LOG("GetStatusForPolicy minHdcpVersion = %s.",
+ NS_ConvertUTF16toUTF8(aPolicy.mMinHdcpVersion).get());
+ mProxy->GetStatusForPolicy(StorePromise(promise), aPolicy.mMinHdcpVersion);
+ return promise.forget();
+}
+
+void MediaKeys::ResolvePromiseWithKeyStatus(PromiseId aId,
+ MediaKeyStatus aMediaKeyStatus) {
+ RefPtr<DetailedPromise> promise(RetrievePromise(aId));
+ if (!promise) {
+ return;
+ }
+ RefPtr<MediaKeys> keys(this);
+ EME_LOG(
+ "MediaKeys[%p]::ResolvePromiseWithKeyStatus() resolve promise id=%" PRIu32
+ ", keystatus=%" PRIu8,
+ this, aId, static_cast<uint8_t>(aMediaKeyStatus));
+ promise->MaybeResolve(aMediaKeyStatus);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/eme/MediaKeys.h b/dom/media/eme/MediaKeys.h
new file mode 100644
index 0000000000..5a44b3c227
--- /dev/null
+++ b/dom/media/eme/MediaKeys.h
@@ -0,0 +1,236 @@
+/* -*- 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 "DecoderDoctorLogger.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DetailedPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/MediaKeyStatusMapBinding.h" // For MediaKeyStatus
+#include "mozilla/dom/MediaKeySystemAccessBinding.h"
+#include "mozilla/dom/MediaKeysBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIObserver.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTHashMap.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+
+class CDMProxy;
+
+namespace dom {
+class MediaKeys;
+} // namespace dom
+DDLoggedTypeName(dom::MediaKeys);
+
+namespace dom {
+
+class ArrayBufferViewOrArrayBuffer;
+class MediaKeySession;
+struct MediaKeysPolicy;
+class HTMLMediaElement;
+
+typedef nsRefPtrHashtable<nsStringHashKey, MediaKeySession> KeySessionHashMap;
+typedef nsRefPtrHashtable<nsUint32HashKey, dom::DetailedPromise> PromiseHashMap;
+typedef nsRefPtrHashtable<nsUint32HashKey, MediaKeySession>
+ PendingKeySessionsHashMap;
+typedef nsTHashMap<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 nsIObserver,
+ public nsWrapperCache,
+ public SupportsWeakPtr,
+ public DecoderDoctorLifeLogger<MediaKeys> {
+ ~MediaKeys();
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(MediaKeys)
+
+ NS_DECL_NSIOBSERVER
+
+ 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();
+
+ // Checks if there's any activity happening that could capture the media
+ // the keys are associated with and then expose that media outside of the
+ // origin it is in.
+ //
+ // This method does not return the results of the check, but the MediaKeys
+ // will notify mProxy of the results using `NotifyOutputProtectionStatus`.
+ void CheckIsElementCapturePossible();
+
+ // Javascript: readonly attribute DOMString keySystem;
+ void GetKeySystem(nsString& retval) const;
+
+ // JavaScript: MediaKeys.createSession()
+ already_AddRefed<MediaKeySession> CreateSession(
+ 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 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 the given exception.
+ void RejectPromise(PromiseId aId, ErrorResult&& aException,
+ const nsCString& aReason);
+ // Resolves promise with "undefined".
+ void ResolvePromise(PromiseId aId);
+
+ 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;
+
+ // Indicates to a MediaKeys instance that the inner window parent of that
+ // instance is being destroyed, this should prompt the keys to shutdown.
+ void OnInnerWindowDestroy();
+
+ void GetSessionsInfo(nsString& sessionsInfo);
+
+ // JavaScript: MediaKeys.GetStatusForPolicy()
+ already_AddRefed<Promise> GetStatusForPolicy(const MediaKeysPolicy& aPolicy,
+ ErrorResult& aR);
+ // Called by CDMProxy when CDM successfully GetStatusForPolicy.
+ void ResolvePromiseWithKeyStatus(PromiseId aId,
+ dom::MediaKeyStatus aMediaKeyStatus);
+
+ template <typename T>
+ void ResolvePromiseWithResult(PromiseId aId, const T& aResult) {
+ RefPtr<DetailedPromise> promise(RetrievePromise(aId));
+ if (!promise) {
+ return;
+ }
+ promise->MaybeResolve(aResult);
+ }
+
+ // The topic used for requests related to mediakeys -- observe this to be
+ // notified of such requests.
+ constexpr static const char* kMediaKeysRequestTopic = "mediakeys-request";
+
+ private:
+ // Instantiate CDMProxy instance.
+ // It could be MediaDrmCDMProxy (Widevine on Fennec) or ChromiumCDMProxy (the
+ // rest).
+ already_AddRefed<CDMProxy> CreateCDMProxy();
+
+ // Removes promise from mPromises, and returns it.
+ already_AddRefed<DetailedPromise> RetrievePromise(PromiseId aId);
+
+ // Helpers to connect and disconnect to the parent inner window. An inner
+ // window should track (via weak ptr) MediaKeys created within it so we can
+ // ensure MediaKeys are shutdown if that window is destroyed.
+ void ConnectInnerWindow();
+ void DisconnectInnerWindow();
+
+ // 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;
+
+ // The HTMLMediaElement the MediaKeys are associated with. Note that a
+ // MediaKeys instance may not be associated with any HTMLMediaElement so
+ // this can be null (we also cannot rely on a media element to drive shutdown
+ // for this reason).
+ RefPtr<HTMLMediaElement> mElement;
+
+ // The inner window associated with an instance of MediaKeys. We will
+ // shutdown the media keys when this Window is destroyed. We do this from the
+ // window rather than a document to address the case where media keys can be
+ // created in an about:blank document that then performs an async load -- this
+ // recreates the document, but the inner window is preserved in such a case.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1675360 for more info.
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ const nsString mKeySystem;
+ KeySessionHashMap mKeySessions;
+ PromiseHashMap mPromises;
+ PendingKeySessionsHashMap mPendingSessions;
+ PromiseId mCreatePromiseId;
+
+ // The principal of the relevant settings object.
+ RefPtr<nsIPrincipal> mPrincipal;
+ // The principal of the top level page. This can differ from mPrincipal if
+ // we're in an iframe.
+ RefPtr<nsIPrincipal> mTopLevelPrincipal;
+
+ const MediaKeySystemConfiguration mConfig;
+
+ PendingPromiseIdTokenHashMap mPromiseIdToken;
+
+ // The topic a MediaKeys instance will observe to receive updates from
+ // EncryptedMediaChild.
+ constexpr static const char* kMediaKeysResponseTopic = "mediakeys-response";
+ // Tracks if we've added an observer for responses from the associated
+ // EncryptedMediaChild. When true an observer is already in place, otherwise
+ // the observer has not yet been added.
+ bool mObserverAdded = false;
+ // Stores the json request we will send to EncryptedMediaChild when querying
+ // output protection. Lazily populated upon first use.
+ nsString mCaptureCheckRequestJson;
+};
+
+} // 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 0000000000..e5431f50fd
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.cpp
@@ -0,0 +1,115 @@
+/* -*- 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 "MediaDrmCDMProxy.h"
+#include "nsString.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "nsContentCID.h"
+#include "nsServiceManagerUtils.h"
+#include "MainThreadUtils.h"
+#include "mozilla/EMEUtils.h"
+
+namespace mozilla {
+
+MediaDrmCDMCallbackProxy::MediaDrmCDMCallbackProxy(MediaDrmCDMProxy* aProxy)
+ : mProxy(aProxy) {}
+
+MediaDrmCDMCallbackProxy::~MediaDrmCDMCallbackProxy() = default;
+
+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,
+ ErrorResult&& aException,
+ const nsCString& aMessage) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mProxy->OnRejectPromise(aPromiseId, std::move(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.Clone());
+ 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;
+ {
+ auto caps = mProxy->Capabilites().Lock();
+ 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;
+ {
+ auto caps = mProxy->Capabilites().Lock();
+ 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
diff --git a/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.h b/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.h
new file mode 100644
index 0000000000..e963e21e11
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.h
@@ -0,0 +1,63 @@
+/* -*- 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;
+class ErrorResult;
+class MediaDrmCDMProxy;
+
+// 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 final : public DecryptorProxyCallback {
+ public:
+ 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, ErrorResult&& 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(MediaDrmCDMProxy* aProxy);
+
+ void BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos);
+ const RefPtr<MediaDrmCDMProxy> mProxy;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp b/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
new file mode 100644
index 0000000000..07b7bfe3a0
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
@@ -0,0 +1,453 @@
+/* -*- 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"
+
+namespace mozilla {
+
+MediaDrmSessionType ToMediaDrmSessionType(
+ dom::MediaKeySessionType aSessionType) {
+ switch (aSessionType) {
+ case dom::MediaKeySessionType::Temporary:
+ return kKeyStreaming;
+ case dom::MediaKeySessionType::Persistent_license:
+ return kKeyOffline;
+ default:
+ return kKeyStreaming;
+ };
+}
+
+MediaDrmCDMProxy::MediaDrmCDMProxy(dom::MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+ : CDMProxy(aKeys, aKeySystem, aDistinctiveIdentifierRequired,
+ aPersistentStateRequired),
+ mCDM(nullptr),
+ mShutdownCalled(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_COUNT_CTOR(MediaDrmCDMProxy);
+}
+
+MediaDrmCDMProxy::~MediaDrmCDMProxy() { MOZ_COUNT_DTOR(MediaDrmCDMProxy); }
+
+void MediaDrmCDMProxy::Init(PromiseId aPromiseId, const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aName) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ EME_LOG("MediaDrmCDMProxy::Init (%s, %s) %s",
+ NS_ConvertUTF16toUTF8(aOrigin).get(),
+ NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
+ NS_ConvertUTF16toUTF8(aName).get());
+
+ // Create a thread to work with cdm.
+ if (!mOwnerThread) {
+ nsresult rv =
+ NS_NewNamedThread("MDCDMThread", getter_AddRefs(mOwnerThread));
+ if (NS_FAILED(rv)) {
+ RejectPromiseWithStateError(
+ aPromiseId, nsLiteralCString(
+ "Couldn't create CDM thread MediaDrmCDMProxy::Init"));
+ return;
+ }
+ }
+
+ mCDM = mozilla::MakeUnique<MediaDrmProxySupport>(mKeySystem);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<uint32_t>("MediaDrmCDMProxy::md_Init", this,
+ &MediaDrmCDMProxy::md_Init, aPromiseId));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void MediaDrmCDMProxy::CreateSession(uint32_t aCreateSessionToken,
+ MediaKeySessionType aSessionType,
+ PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ UniquePtr<CreateSessionData> data(new CreateSessionData());
+ data->mSessionType = aSessionType;
+ data->mCreateSessionToken = aCreateSessionToken;
+ data->mPromiseId = aPromiseId;
+ data->mInitDataType = NS_ConvertUTF16toUTF8(aInitDataType);
+ data->mInitData = std::move(aInitData);
+
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<CreateSessionData>&&>(
+ "MediaDrmCDMProxy::md_CreateSession", this,
+ &MediaDrmCDMProxy::md_CreateSession, std::move(data)));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void MediaDrmCDMProxy::LoadSession(PromiseId aPromiseId,
+ dom::MediaKeySessionType aSessionType,
+ const nsAString& aSessionId) {
+ // TODO: Implement LoadSession.
+ RejectPromiseWithStateError(
+ aPromiseId, "Currently Fennec does not support LoadSession"_ns);
+}
+
+void MediaDrmCDMProxy::SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ mOwnerThread->Dispatch(NewRunnableMethod<PromiseId, const nsTArray<uint8_t>>(
+ "MediaDrmCDMProxy::md_SetServerCertificate", this,
+ &MediaDrmCDMProxy::md_SetServerCertificate,
+ aPromiseId, std::move(aCert)),
+ NS_DISPATCH_NORMAL);
+}
+
+void MediaDrmCDMProxy::UpdateSession(const nsAString& aSessionId,
+ PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ UniquePtr<UpdateSessionData> data(new UpdateSessionData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ data->mResponse = std::move(aResponse);
+
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<UpdateSessionData>&&>(
+ "MediaDrmCDMProxy::md_UpdateSession", this,
+ &MediaDrmCDMProxy::md_UpdateSession, std::move(data)));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void MediaDrmCDMProxy::CloseSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ UniquePtr<SessionOpData> data(new SessionOpData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<SessionOpData>&&>(
+ "MediaDrmCDMProxy::md_CloseSession", this,
+ &MediaDrmCDMProxy::md_CloseSession, std::move(data)));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void MediaDrmCDMProxy::RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) {
+ // TODO: Implement RemoveSession.
+ RejectPromiseWithStateError(
+ aPromiseId, "Currently Fennec does not support RemoveSession"_ns);
+}
+
+void MediaDrmCDMProxy::QueryOutputProtectionStatus() {
+ // TODO(bryce): determine if this is needed for Android and implement as
+ // needed. See also `NotifyOutputProtectionStatus`.
+}
+
+void MediaDrmCDMProxy::NotifyOutputProtectionStatus(
+ OutputProtectionCheckStatus aCheckStatus,
+ OutputProtectionCaptureStatus aCaptureStatus) {
+ // TODO(bryce): determine if this is needed for Android and implement as
+ // needed. See also `QueryOutputProtectionStatus`.
+}
+
+void MediaDrmCDMProxy::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod(
+ "MediaDrmCDMProxy::md_Shutdown", this, &MediaDrmCDMProxy::md_Shutdown));
+
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+ mOwnerThread->Shutdown();
+ mOwnerThread = nullptr;
+ mKeys.Clear();
+}
+
+void MediaDrmCDMProxy::Terminated() {
+ // TODO: Implement Terminated.
+ // Should find a way to handle the case when remote side MediaDrm crashed.
+}
+
+void MediaDrmCDMProxy::OnSetSessionId(uint32_t aCreateSessionToken,
+ const nsAString& aSessionId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+
+ RefPtr<dom::MediaKeySession> session(
+ mKeys->GetPendingSession(aCreateSessionToken));
+ if (session) {
+ session->SetSessionId(aSessionId);
+ }
+}
+
+void MediaDrmCDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ mKeys->OnSessionLoaded(aPromiseId, aSuccess);
+}
+
+void MediaDrmCDMProxy::OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyMessage(aMessageType, aMessage);
+ }
+}
+
+void MediaDrmCDMProxy::OnExpirationChange(const nsAString& aSessionId,
+ UnixTime aExpiryTime) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->SetExpiration(static_cast<double>(aExpiryTime));
+ }
+}
+
+void MediaDrmCDMProxy::OnSessionClosed(const nsAString& aSessionId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->OnClosed();
+ }
+}
+
+void MediaDrmCDMProxy::OnSessionError(const nsAString& aSessionId,
+ nsresult aException, uint32_t aSystemCode,
+ const nsAString& aMsg) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyError(aSystemCode);
+ }
+}
+
+void MediaDrmCDMProxy::OnRejectPromise(uint32_t aPromiseId,
+ ErrorResult&& aException,
+ const nsCString& aMsg) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RejectPromise(aPromiseId, std::move(aException), aMsg);
+}
+
+RefPtr<DecryptPromise> MediaDrmCDMProxy::Decrypt(MediaRawData* aSample) {
+ MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypting individually");
+ return nullptr;
+}
+
+void MediaDrmCDMProxy::OnDecrypted(uint32_t aId, DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) {
+ MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypted event");
+}
+
+void MediaDrmCDMProxy::RejectPromise(PromiseId aId, ErrorResult&& aException,
+ const nsCString& aReason) {
+ if (NS_IsMainThread()) {
+ if (!mKeys.IsNull()) {
+ mKeys->RejectPromise(aId, std::move(aException), aReason);
+ }
+ } else {
+ nsCOMPtr<nsIRunnable> task(
+ new RejectPromiseTask(this, aId, std::move(aException), aReason));
+ mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+ }
+}
+
+void MediaDrmCDMProxy::RejectPromiseWithStateError(PromiseId aId,
+ const nsCString& aReason) {
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(aReason);
+ RejectPromise(aId, std::move(rv), aReason);
+}
+
+void MediaDrmCDMProxy::ResolvePromise(PromiseId aId) {
+ if (NS_IsMainThread()) {
+ if (!mKeys.IsNull()) {
+ mKeys->ResolvePromise(aId);
+ } else {
+ NS_WARNING("MediaDrmCDMProxy unable to resolve promise!");
+ }
+ } else {
+ nsCOMPtr<nsIRunnable> task;
+ task =
+ NewRunnableMethod<PromiseId>("MediaDrmCDMProxy::ResolvePromise", this,
+ &MediaDrmCDMProxy::ResolvePromise, aId);
+ mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+ }
+}
+
+template <typename T>
+void MediaDrmCDMProxy::ResolvePromiseWithResult(PromiseId aId,
+ const T& aResult) {
+ if (NS_IsMainThread()) {
+ if (!mKeys.IsNull()) {
+ mKeys->ResolvePromiseWithResult(aId, aResult);
+ } else {
+ NS_WARNING("MediaDrmCDMProxy unable to resolve promise!");
+ }
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> task;
+ task = NewRunnableMethod<PromiseId, T>(
+ "MediaDrmCDMProxy::ResolvePromiseWithResult", this,
+ &MediaDrmCDMProxy::ResolvePromiseWithResult<T>, aId, aResult);
+ mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+}
+
+void MediaDrmCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyStatusesChange();
+ }
+}
+
+void MediaDrmCDMProxy::GetStatusForPolicy(PromiseId aPromiseId,
+ const nsAString& aMinHdcpVersion) {
+ // TODO: Implement GetStatusForPolicy.
+ constexpr auto err =
+ "Currently Fennec does not support GetStatusForPolicy"_ns;
+
+ ErrorResult rv;
+ rv.ThrowNotSupportedError(err);
+ RejectPromise(aPromiseId, std::move(rv), err);
+}
+
+#ifdef DEBUG
+bool MediaDrmCDMProxy::IsOnOwnerThread() {
+ return NS_GetCurrentThread() == mOwnerThread;
+}
+#endif
+
+const nsString& MediaDrmCDMProxy::GetMediaDrmStubId() const {
+ MOZ_ASSERT(mCDM);
+ return mCDM->GetMediaDrmStubId();
+}
+
+void MediaDrmCDMProxy::OnCDMCreated(uint32_t aPromiseId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+
+ if (mCDM) {
+ mKeys->OnCDMCreated(aPromiseId, 0);
+ return;
+ }
+
+ // No CDM? Just reject the promise.
+ constexpr auto err = "Null CDM in OnCDMCreated()"_ns;
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(err);
+ mKeys->RejectPromise(aPromiseId, std::move(rv), err);
+}
+
+void MediaDrmCDMProxy::md_Init(uint32_t aPromiseId) {
+ MOZ_ASSERT(IsOnOwnerThread());
+ MOZ_ASSERT(mCDM);
+
+ UniquePtr<MediaDrmCDMCallbackProxy> callback(
+ new MediaDrmCDMCallbackProxy(this));
+ mCDM->Init(std::move(callback));
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<uint32_t>("MediaDrmCDMProxy::OnCDMCreated", this,
+ &MediaDrmCDMProxy::OnCDMCreated, aPromiseId));
+ mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+}
+
+void MediaDrmCDMProxy::md_CreateSession(UniquePtr<CreateSessionData>&& aData) {
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromiseWithStateError(aData->mPromiseId,
+ "Null CDM in md_CreateSession"_ns);
+ return;
+ }
+
+ mCDM->CreateSession(aData->mCreateSessionToken, aData->mPromiseId,
+ aData->mInitDataType, aData->mInitData,
+ ToMediaDrmSessionType(aData->mSessionType));
+}
+
+void MediaDrmCDMProxy::md_SetServerCertificate(PromiseId aPromiseId,
+ const nsTArray<uint8_t>& aCert) {
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromiseWithStateError(aPromiseId,
+ "Null CDM in md_SetServerCertificate"_ns);
+ return;
+ }
+
+ if (mCDM->SetServerCertificate(aCert)) {
+ ResolvePromiseWithResult(aPromiseId, true);
+ } else {
+ RejectPromiseWithStateError(
+ aPromiseId, "MediaDrmCDMProxy unable to set server certificate"_ns);
+ }
+}
+
+void MediaDrmCDMProxy::md_UpdateSession(UniquePtr<UpdateSessionData>&& aData) {
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromiseWithStateError(aData->mPromiseId,
+ "Null CDM in md_UpdateSession"_ns);
+ return;
+ }
+ mCDM->UpdateSession(aData->mPromiseId, aData->mSessionId, aData->mResponse);
+}
+
+void MediaDrmCDMProxy::md_CloseSession(UniquePtr<SessionOpData>&& aData) {
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromiseWithStateError(aData->mPromiseId,
+ "Null CDM in md_CloseSession"_ns);
+ return;
+ }
+ mCDM->CloseSession(aData->mPromiseId, aData->mSessionId);
+}
+
+void MediaDrmCDMProxy::md_Shutdown() {
+ MOZ_ASSERT(IsOnOwnerThread());
+ MOZ_ASSERT(mCDM);
+ if (mShutdownCalled) {
+ return;
+ }
+ mShutdownCalled = true;
+ mCDM->Shutdown();
+ mCDM = nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/media/eme/mediadrm/MediaDrmCDMProxy.h b/dom/media/eme/mediadrm/MediaDrmCDMProxy.h
new file mode 100644
index 0000000000..60a541ece9
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.h
@@ -0,0 +1,186 @@
+/* -*- 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 "mozilla/CDMProxy.h"
+#include "mozilla/CDMCaps.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/MediaDrmProxySupport.h"
+#include "mozilla/UniquePtr.h"
+
+#include "MediaCodec.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+class MediaDrmCDMCallbackProxy;
+class MediaDrmCDMProxy final : public CDMProxy {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDrmCDMProxy, override)
+
+ MediaDrmCDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired);
+
+ void Init(PromiseId aPromiseId, const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aGMPName) override;
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ MediaKeySessionType aSessionType, PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData) override;
+
+ void LoadSession(PromiseId aPromiseId, dom::MediaKeySessionType aSessionType,
+ 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 QueryOutputProtectionStatus() override;
+
+ void NotifyOutputProtectionStatus(
+ OutputProtectionCheckStatus aCheckStatus,
+ OutputProtectionCaptureStatus aCaptureStatus) override;
+
+ void Shutdown() override;
+
+ void Terminated() 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,
+ const 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, ErrorResult&& aException,
+ 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, ErrorResult&& aException,
+ const nsCString& aReason) override;
+ // Reject promise with an InvalidStateError and the given message.
+ void RejectPromiseWithStateError(PromiseId aId, const nsCString& aReason);
+
+ // Resolves promise with "undefined".
+ // Can be called from any thread.
+ void ResolvePromise(PromiseId aId) override;
+
+ void OnKeyStatusesChange(const nsAString& aSessionId) override;
+
+ void GetStatusForPolicy(PromiseId aPromiseId,
+ const nsAString& aMinHdcpVersion) override;
+
+#ifdef DEBUG
+ bool IsOnOwnerThread() override;
+#endif
+
+ const nsString& GetMediaDrmStubId() const;
+
+ private:
+ virtual ~MediaDrmCDMProxy();
+
+ void OnCDMCreated(uint32_t aPromiseId);
+
+ template <typename T>
+ void ResolvePromiseWithResult(PromiseId aId, const T& aResult);
+
+ 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,
+ ErrorResult&& aException, const nsCString& aReason)
+ : Runnable("RejectPromiseTask"),
+ mProxy(aProxy),
+ mId(aId),
+ mException(std::move(aException)),
+ mReason(aReason) {}
+ NS_IMETHOD Run() override {
+ // Moving into or out of a non-copyable ErrorResult will assert that both
+ // ErorResults are from our current thread. Avoid the assertion by moving
+ // into a current-thread CopyableErrorResult first. Note that this is
+ // safe, because CopyableErrorResult never holds state that can't move
+ // across threads.
+ CopyableErrorResult rv(std::move(mException));
+ mProxy->RejectPromise(mId, std::move(rv), mReason);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<MediaDrmCDMProxy> mProxy;
+ PromiseId mId;
+ // We use a CopyableErrorResult here, because we're going to dispatch to a
+ // different thread and normal ErrorResult doesn't support that.
+ // CopyableErrorResult ensures that it only stores values that are safe to
+ // move across threads.
+ CopyableErrorResult mException;
+ nsCString mReason;
+ };
+
+ nsCString mNodeId;
+ UniquePtr<MediaDrmProxySupport> mCDM;
+ bool mShutdownCalled;
+
+ // =====================================================================
+ // For MediaDrmProxySupport
+ void md_Init(uint32_t aPromiseId);
+ void md_CreateSession(UniquePtr<CreateSessionData>&& aData);
+ void md_SetServerCertificate(PromiseId aPromiseId,
+ const nsTArray<uint8_t>& aCert);
+ void md_UpdateSession(UniquePtr<UpdateSessionData>&& aData);
+ void md_CloseSession(UniquePtr<SessionOpData>&& aData);
+ void md_Shutdown();
+ // =====================================================================
+};
+
+} // namespace mozilla
+
+#endif // MediaDrmCDMProxy_h_
diff --git a/dom/media/eme/mediadrm/MediaDrmProxySupport.cpp b/dom/media/eme/mediadrm/MediaDrmProxySupport.cpp
new file mode 100644
index 0000000000..717a57df35
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmProxySupport.cpp
@@ -0,0 +1,272 @@
+/* -*- 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 "MediaDrmCDMCallbackProxy.h"
+#include "mozilla/EMEUtils.h"
+#include "mozilla/java/MediaDrmProxyNatives.h"
+#include "mozilla/java/SessionKeyInfoWrappers.h"
+#include "MediaCodec.h" // For MediaDrm::KeyStatus
+
+namespace mozilla {
+
+LogModule* GetMDRMNLog() {
+ static LazyLogModule log("MediaDrmProxySupport");
+ return log;
+}
+
+class MediaDrmJavaCallbacksSupport
+ : public java::MediaDrmProxy::NativeMediaDrmProxyCallbacks::Natives<
+ MediaDrmJavaCallbacksSupport> {
+ public:
+ typedef java::MediaDrmProxy::NativeMediaDrmProxyCallbacks::Natives<
+ MediaDrmJavaCallbacksSupport>
+ MediaDrmProxyNativeCallbacks;
+ using MediaDrmProxyNativeCallbacks::AttachNative;
+ using MediaDrmProxyNativeCallbacks::DisposeNative;
+
+ explicit MediaDrmJavaCallbacksSupport(
+ UniquePtr<MediaDrmCDMCallbackProxy>&& aDecryptorProxyCallback)
+ : mDecryptorProxyCallback(std::move(aDecryptorProxyCallback)) {
+ MOZ_ASSERT(mDecryptorProxyCallback);
+ }
+ /*
+ * 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:
+ UniquePtr<MediaDrmCDMCallbackProxy> 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::MediaDrm;
+ switch (aStatusCode) {
+ case MediaDrm::KeyStatus::STATUS_USABLE:
+ return dom::MediaKeyStatus::Usable;
+ case MediaDrm::KeyStatus::STATUS_EXPIRED:
+ return dom::MediaKeyStatus::Expired;
+ case MediaDrm::KeyStatus::STATUS_OUTPUT_NOT_ALLOWED:
+ return dom::MediaKeyStatus::Output_restricted;
+ case MediaDrm::KeyStatus::STATUS_INTERNAL_ERROR:
+ return dom::MediaKeyStatus::Internal_error;
+ case MediaDrm::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(std::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.
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(reason);
+ mDecryptorProxyCallback->RejectPromise(aPromiseId, std::move(rv), reason);
+}
+
+MediaDrmProxySupport::MediaDrmProxySupport(const nsAString& aKeySystem)
+ : mKeySystem(aKeySystem), mDestroyed(false) {
+ mJavaCallbacks = java::MediaDrmProxy::NativeMediaDrmProxyCallbacks::New();
+
+ mBridgeProxy = java::MediaDrmProxy::Create(mKeySystem, mJavaCallbacks);
+
+ MOZ_ASSERT(mBridgeProxy, "mBridgeProxy should not be null");
+ mMediaDrmStubId = mBridgeProxy->GetStubId()->ToString();
+}
+
+MediaDrmProxySupport::~MediaDrmProxySupport() {
+ MOZ_ASSERT(mDestroyed, "Shutdown() should be called before !!");
+ MediaDrmJavaCallbacksSupport::DisposeNative(mJavaCallbacks);
+}
+
+nsresult MediaDrmProxySupport::Init(
+ UniquePtr<MediaDrmCDMCallbackProxy>&& aCallback) {
+ MOZ_ASSERT(mJavaCallbacks);
+
+ MediaDrmJavaCallbacksSupport::AttachNative(
+ mJavaCallbacks,
+ mozilla::MakeUnique<MediaDrmJavaCallbacksSupport>(std::move(aCallback)));
+ 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;
+}
+
+bool MediaDrmProxySupport::SetServerCertificate(
+ const nsTArray<uint8_t>& aCert) {
+ jni::ByteArray::LocalRef cert = jni::ByteArray::New(
+ reinterpret_cast<const int8_t*>(aCert.Elements()), aCert.Length());
+ return mBridgeProxy->SetServerCertificate(cert);
+}
+
+} // namespace mozilla
diff --git a/dom/media/eme/mediadrm/MediaDrmProxySupport.h b/dom/media/eme/mediadrm/MediaDrmProxySupport.h
new file mode 100644
index 0000000000..e994036f69
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmProxySupport.h
@@ -0,0 +1,68 @@
+/* -*- 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/java/MediaDrmProxyWrappers.h"
+#include "mozilla/Logging.h"
+#include "mozilla/UniquePtr.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 MediaDrmCDMCallbackProxy;
+
+class MediaDrmProxySupport final {
+ public:
+ explicit MediaDrmProxySupport(const nsAString& aKeySystem);
+ ~MediaDrmProxySupport();
+
+ /*
+ * APIs to act as GMPDecryptorAPI, discarding unnecessary calls.
+ */
+ nsresult Init(UniquePtr<MediaDrmCDMCallbackProxy>&& 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();
+
+ const nsString& GetMediaDrmStubId() const { return mMediaDrmStubId; }
+
+ bool SetServerCertificate(const nsTArray<uint8_t>& aCert);
+
+ private:
+ const nsString mKeySystem;
+ java::MediaDrmProxy::GlobalRef mBridgeProxy;
+ java::MediaDrmProxy::NativeMediaDrmProxyCallbacks::GlobalRef mJavaCallbacks;
+ bool mDestroyed;
+ nsString mMediaDrmStubId;
+};
+
+} // namespace mozilla
+#endif // MediaDrmProxySupport_H
diff --git a/dom/media/eme/mediadrm/moz.build b/dom/media/eme/mediadrm/moz.build
new file mode 100644
index 0000000000..c8f433f25f
--- /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"
diff --git a/dom/media/eme/mediafoundation/WMFCDMImpl.cpp b/dom/media/eme/mediafoundation/WMFCDMImpl.cpp
new file mode 100644
index 0000000000..c2ca31bf15
--- /dev/null
+++ b/dom/media/eme/mediafoundation/WMFCDMImpl.cpp
@@ -0,0 +1,128 @@
+/* -*- 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 "WMFCDMImpl.h"
+
+#include "mozilla/dom/MediaKeySession.h"
+
+namespace mozilla {
+
+/* static */
+bool WMFCDMImpl::Supports(const nsAString& aKeySystem) {
+ static std::map<nsString, bool> supports;
+
+ nsString key(aKeySystem);
+ if (const auto& s = supports.find(key); s != supports.end()) {
+ return s->second;
+ }
+
+ RefPtr<WMFCDMImpl> cdm = MakeRefPtr<WMFCDMImpl>(aKeySystem);
+ KeySystemConfig c;
+ bool s = cdm->GetCapabilities(c);
+ supports[key] = s;
+
+ return s;
+}
+
+static void MFCDMCapabilitiesIPDLToKeySystemConfig(
+ const MFCDMCapabilitiesIPDL& aCDMConfig,
+ KeySystemConfig& aKeySystemConfig) {
+ aKeySystemConfig.mKeySystem = aCDMConfig.keySystem();
+
+ for (const auto& type : aCDMConfig.initDataTypes()) {
+ aKeySystemConfig.mInitDataTypes.AppendElement(type);
+ }
+
+ for (const auto& type : aCDMConfig.sessionTypes()) {
+ aKeySystemConfig.mSessionTypes.AppendElement(type);
+ }
+
+ for (const auto& c : aCDMConfig.videoCapabilities()) {
+ if (!c.robustness().IsEmpty() &&
+ !aKeySystemConfig.mVideoRobustness.Contains(c.robustness())) {
+ aKeySystemConfig.mVideoRobustness.AppendElement(c.robustness());
+ }
+ aKeySystemConfig.mMP4.SetCanDecryptAndDecode(
+ NS_ConvertUTF16toUTF8(c.contentType()));
+ }
+ for (const auto& c : aCDMConfig.audioCapabilities()) {
+ if (!c.robustness().IsEmpty() &&
+ !aKeySystemConfig.mAudioRobustness.Contains(c.robustness())) {
+ aKeySystemConfig.mAudioRobustness.AppendElement(c.robustness());
+ }
+ aKeySystemConfig.mMP4.SetCanDecryptAndDecode(
+ NS_ConvertUTF16toUTF8(c.contentType()));
+ }
+ aKeySystemConfig.mPersistentState = aCDMConfig.persistentState();
+ aKeySystemConfig.mDistinctiveIdentifier = aCDMConfig.distinctiveID();
+}
+
+static const char* EncryptionSchemeStr(const CryptoScheme aScheme) {
+ switch (aScheme) {
+ case CryptoScheme::None:
+ return "none";
+ case CryptoScheme::Cenc:
+ return "cenc";
+ case CryptoScheme::Cbcs:
+ return "cbcs";
+ }
+}
+
+bool WMFCDMImpl::GetCapabilities(KeySystemConfig& aConfig) {
+ nsCOMPtr<nsISerialEventTarget> backgroundTaskQueue;
+ NS_CreateBackgroundTaskQueue(__func__, getter_AddRefs(backgroundTaskQueue));
+
+ bool ok = false;
+ media::Await(
+ backgroundTaskQueue.forget(), mCDM->GetCapabilities(),
+ [&ok, &aConfig](const MFCDMCapabilitiesIPDL& capabilities) {
+ EME_LOG("capabilities: keySystem=%s",
+ NS_ConvertUTF16toUTF8(capabilities.keySystem()).get());
+ for (const auto& v : capabilities.videoCapabilities()) {
+ EME_LOG("capabilities: video=%s",
+ NS_ConvertUTF16toUTF8(v.contentType()).get());
+ }
+ for (const auto& a : capabilities.audioCapabilities()) {
+ EME_LOG("capabilities: audio=%s",
+ NS_ConvertUTF16toUTF8(a.contentType()).get());
+ }
+ for (const auto& v : capabilities.encryptionSchemes()) {
+ EME_LOG("capabilities: encryptionScheme=%s", EncryptionSchemeStr(v));
+ }
+ MFCDMCapabilitiesIPDLToKeySystemConfig(capabilities, aConfig);
+ ok = true;
+ },
+ [](nsresult rv) {
+ EME_LOG("Fail to get key system capabilities. rv=%x", rv);
+ });
+ return ok;
+}
+
+RefPtr<WMFCDMImpl::InitPromise> WMFCDMImpl::Init(
+ const WMFCDMImpl::InitParams& aParams) {
+ MOZ_ASSERT(mCDM);
+
+ RefPtr<WMFCDMImpl> self = this;
+ mCDM->Init(aParams.mOrigin, aParams.mInitDataTypes,
+ aParams.mPersistentStateRequired
+ ? KeySystemConfig::Requirement::Required
+ : KeySystemConfig::Requirement::Optional,
+ aParams.mDistinctiveIdentifierRequired
+ ? KeySystemConfig::Requirement::Required
+ : KeySystemConfig::Requirement::Optional,
+ aParams.mHWSecure, aParams.mProxyCallback)
+ ->Then(
+ mCDM->ManagerThread(), __func__,
+ [self, this](const MFCDMInitIPDL& init) {
+ mInitPromiseHolder.ResolveIfExists(true, __func__);
+ },
+ [self, this](const nsresult rv) {
+ mInitPromiseHolder.RejectIfExists(rv, __func__);
+ });
+ return mInitPromiseHolder.Ensure(__func__);
+}
+
+} // namespace mozilla
diff --git a/dom/media/eme/mediafoundation/WMFCDMImpl.h b/dom/media/eme/mediafoundation/WMFCDMImpl.h
new file mode 100644
index 0000000000..fc4ef840d6
--- /dev/null
+++ b/dom/media/eme/mediafoundation/WMFCDMImpl.h
@@ -0,0 +1,100 @@
+/* -*- 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 DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMIMPL_H_
+#define DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMIMPL_H_
+
+#include "MediaData.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/EMEUtils.h"
+#include "mozilla/KeySystemConfig.h"
+#include "mozilla/media/MediaUtils.h"
+#include "mozilla/MFCDMChild.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+class WMFCDMProxyCallback;
+
+/**
+ * WMFCDMImpl is a helper class for MFCDM protocol clients. It creates, manages,
+ * and calls MFCDMChild object in the content process on behalf of the client,
+ * and performs conversion between EME and MFCDM types and constants.
+ */
+class WMFCDMImpl final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WMFCDMImpl);
+
+ explicit WMFCDMImpl(const nsAString& aKeySystem)
+ : mCDM(MakeRefPtr<MFCDMChild>(aKeySystem)) {}
+
+ static bool Supports(const nsAString& aKeySystem);
+ // TODO: make this async?
+ bool GetCapabilities(KeySystemConfig& aConfig);
+
+ using InitPromise = GenericPromise;
+ struct InitParams {
+ nsString mOrigin;
+ CopyableTArray<nsString> mInitDataTypes;
+ bool mPersistentStateRequired;
+ bool mDistinctiveIdentifierRequired;
+ bool mHWSecure;
+ WMFCDMProxyCallback* mProxyCallback;
+ };
+
+ RefPtr<InitPromise> Init(const InitParams& aParams);
+
+ RefPtr<MFCDMChild::SessionPromise> CreateSession(
+ uint32_t aPromiseId, const KeySystemConfig::SessionType aSessionType,
+ const nsAString& aInitDataType, const nsTArray<uint8_t>& aInitData) {
+ return mCDM->CreateSessionAndGenerateRequest(aPromiseId, aSessionType,
+ aInitDataType, aInitData);
+ }
+
+ RefPtr<GenericPromise> LoadSession(
+ uint32_t aPromiseId, const KeySystemConfig::SessionType aSessionType,
+ const nsAString& aSessionId) {
+ return mCDM->LoadSession(aPromiseId, aSessionType, aSessionId);
+ }
+
+ RefPtr<GenericPromise> UpdateSession(uint32_t aPromiseId,
+ const nsAString& aSessionId,
+ nsTArray<uint8_t>& aResponse) {
+ return mCDM->UpdateSession(aPromiseId, aSessionId, aResponse);
+ }
+
+ RefPtr<GenericPromise> CloseSession(uint32_t aPromiseId,
+ const nsAString& aSessionId) {
+ return mCDM->CloseSession(aPromiseId, aSessionId);
+ }
+
+ RefPtr<GenericPromise> RemoveSession(uint32_t aPromiseId,
+ const nsAString& aSessionId) {
+ return mCDM->RemoveSession(aPromiseId, aSessionId);
+ }
+
+ uint64_t Id() {
+ MOZ_ASSERT(mCDM->Id() != 0,
+ "Should be called only after Init() is resolved");
+ return mCDM->Id();
+ }
+
+ private:
+ ~WMFCDMImpl() {
+ if (mCDM) {
+ mCDM->Shutdown();
+ }
+ };
+
+ const RefPtr<MFCDMChild> mCDM;
+
+ MozPromiseHolder<InitPromise> mInitPromiseHolder;
+};
+
+} // namespace mozilla
+
+#endif // DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMIMPL_H_
diff --git a/dom/media/eme/mediafoundation/WMFCDMProxy.cpp b/dom/media/eme/mediafoundation/WMFCDMProxy.cpp
new file mode 100644
index 0000000000..1d691168d8
--- /dev/null
+++ b/dom/media/eme/mediafoundation/WMFCDMProxy.cpp
@@ -0,0 +1,306 @@
+/* -*- 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 "WMFCDMProxy.h"
+
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/WMFCDMProxyCallback.h"
+#include "WMFCDMImpl.h"
+#include "WMFCDMProxyCallback.h"
+
+namespace mozilla {
+
+#define LOG(msg, ...) \
+ EME_LOG("WMFCDMProxy[%p]@%s: " msg, this, __func__, ##__VA_ARGS__)
+
+#define RETURN_IF_SHUTDOWN() \
+ do { \
+ MOZ_ASSERT(NS_IsMainThread()); \
+ if (mIsShutdown) { \
+ return; \
+ } \
+ } while (false)
+
+#define PERFORM_ON_CDM(operation, promiseId, ...) \
+ do { \
+ mCDM->operation(promiseId, __VA_ARGS__) \
+ ->Then( \
+ mMainThread, __func__, \
+ [self = RefPtr{this}, this, promiseId]() { \
+ RETURN_IF_SHUTDOWN(); \
+ if (mKeys.IsNull()) { \
+ EME_LOG("WMFCDMProxy(this=%p, pid=%" PRIu32 \
+ ") : abort the " #operation " due to empty key", \
+ this, promiseId); \
+ return; \
+ } \
+ ResolvePromise(promiseId); \
+ }, \
+ [self = RefPtr{this}, this, promiseId]() { \
+ RETURN_IF_SHUTDOWN(); \
+ RejectPromiseWithStateError( \
+ promiseId, nsLiteralCString("WMFCDMProxy::" #operation ": " \
+ "failed to " #operation)); \
+ }); \
+ } while (false)
+
+WMFCDMProxy::WMFCDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem,
+ const dom::MediaKeySystemConfiguration& aConfig)
+ : CDMProxy(
+ aKeys, aKeySystem,
+ aConfig.mDistinctiveIdentifier == dom::MediaKeysRequirement::Required,
+ aConfig.mPersistentState == dom::MediaKeysRequirement::Required),
+ mConfig(aConfig) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+WMFCDMProxy::~WMFCDMProxy() {}
+
+void WMFCDMProxy::Init(PromiseId aPromiseId, const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aName) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+
+ MOZ_ASSERT(!mOwnerThread);
+ if (NS_FAILED(
+ NS_NewNamedThread("WMFCDMThread", getter_AddRefs(mOwnerThread)))) {
+ RejectPromiseWithStateError(
+ aPromiseId,
+ nsLiteralCString("WMFCDMProxy::Init: couldn't create CDM thread"));
+ return;
+ }
+
+ mCDM = MakeRefPtr<WMFCDMImpl>(mKeySystem);
+ mProxyCallback = new WMFCDMProxyCallback(this);
+ WMFCDMImpl::InitParams params{nsString(aOrigin),
+ mConfig.mInitDataTypes,
+ mPersistentStateRequired,
+ mDistinctiveIdentifierRequired,
+ false /* TODO : support HW secure */,
+ mProxyCallback};
+ mCDM->Init(params)->Then(
+ mMainThread, __func__,
+ [self = RefPtr{this}, this, aPromiseId](const bool) {
+ MOZ_ASSERT(mCDM->Id() > 0);
+ mKeys->OnCDMCreated(aPromiseId, mCDM->Id());
+ },
+ [self = RefPtr{this}, this, aPromiseId](const nsresult rv) {
+ RejectPromiseWithStateError(
+ aPromiseId,
+ nsLiteralCString("WMFCDMProxy::Init: WMFCDM init error"));
+ });
+}
+
+void WMFCDMProxy::ResolvePromise(PromiseId aId) {
+ auto resolve = [self = RefPtr{this}, this, aId]() {
+ RETURN_IF_SHUTDOWN();
+ EME_LOG("WMFCDMProxy::ResolvePromise(this=%p, pid=%" PRIu32 ")", this, aId);
+ if (!mKeys.IsNull()) {
+ mKeys->ResolvePromise(aId);
+ } else {
+ NS_WARNING("WMFCDMProxy unable to resolve promise!");
+ }
+ };
+
+ if (NS_IsMainThread()) {
+ resolve();
+ return;
+ }
+ mMainThread->Dispatch(
+ NS_NewRunnableFunction("WMFCDMProxy::ResolvePromise", resolve));
+}
+
+void WMFCDMProxy::RejectPromise(PromiseId aId, ErrorResult&& aException,
+ const nsCString& aReason) {
+ if (!NS_IsMainThread()) {
+ // Use CopyableErrorResult to store our exception in the runnable,
+ // because ErrorResult is not OK to move across threads.
+ mMainThread->Dispatch(
+ NewRunnableMethod<PromiseId, StoreCopyPassByRRef<CopyableErrorResult>,
+ nsCString>("WMFCDMProxy::RejectPromise", this,
+ &WMFCDMProxy::RejectPromiseOnMainThread,
+ aId, std::move(aException), aReason),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+ EME_LOG("WMFCDMProxy::RejectPromise(this=%p, pid=%" PRIu32
+ ", code=0x%x, "
+ "reason='%s')",
+ this, aId, aException.ErrorCodeAsInt(), aReason.get());
+ if (!mKeys.IsNull()) {
+ mKeys->RejectPromise(aId, std::move(aException), aReason);
+ } else {
+ // We don't have a MediaKeys object to pass the exception to, so silence
+ // the exception to avoid it asserting due to being unused.
+ aException.SuppressException();
+ }
+}
+
+void WMFCDMProxy::RejectPromiseOnMainThread(PromiseId aId,
+ CopyableErrorResult&& aException,
+ const nsCString& aReason) {
+ RETURN_IF_SHUTDOWN();
+ // Moving into or out of a non-copyable ErrorResult will assert that both
+ // ErorResults are from our current thread. Avoid the assertion by moving
+ // into a current-thread CopyableErrorResult first. Note that this is safe,
+ // because CopyableErrorResult never holds state that can't move across
+ // threads.
+ CopyableErrorResult rv(std::move(aException));
+ RejectPromise(aId, std::move(rv), aReason);
+}
+
+void WMFCDMProxy::RejectPromiseWithStateError(PromiseId aId,
+ const nsCString& aReason) {
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(aReason);
+ RejectPromise(aId, std::move(rv), aReason);
+}
+
+void WMFCDMProxy::CreateSession(uint32_t aCreateSessionToken,
+ MediaKeySessionType aSessionType,
+ PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RETURN_IF_SHUTDOWN();
+ const auto sessionType = ConvertToKeySystemConfigSessionType(aSessionType);
+ EME_LOG("WMFCDMProxy::CreateSession(this=%p, pid=%" PRIu32
+ "), sessionType=%s",
+ this, aPromiseId, SessionTypeToStr(sessionType));
+ mCDM->CreateSession(aPromiseId, sessionType, aInitDataType, aInitData)
+ ->Then(
+ mMainThread, __func__,
+ [self = RefPtr{this}, this, aCreateSessionToken,
+ aPromiseId](nsString sessionID) {
+ RETURN_IF_SHUTDOWN();
+ if (mKeys.IsNull()) {
+ EME_LOG("WMFCDMProxy(this=%p, pid=%" PRIu32
+ ") : abort the create session due to "
+ "empty key",
+ this, aPromiseId);
+ return;
+ }
+ if (RefPtr<dom::MediaKeySession> session =
+ mKeys->GetPendingSession(aCreateSessionToken)) {
+ session->SetSessionId(std::move(sessionID));
+ }
+ ResolvePromise(aPromiseId);
+ },
+ [self = RefPtr{this}, this, aPromiseId]() {
+ RETURN_IF_SHUTDOWN();
+ RejectPromiseWithStateError(
+ aPromiseId,
+ nsLiteralCString(
+ "WMFCDMProxy::CreateSession: cannot create session"));
+ });
+}
+
+void WMFCDMProxy::LoadSession(PromiseId aPromiseId,
+ dom::MediaKeySessionType aSessionType,
+ const nsAString& aSessionId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RETURN_IF_SHUTDOWN();
+ const auto sessionType = ConvertToKeySystemConfigSessionType(aSessionType);
+ EME_LOG("WMFCDMProxy::LoadSession(this=%p, pid=%" PRIu32
+ "), sessionType=%s, sessionId=%s",
+ this, aPromiseId, SessionTypeToStr(sessionType),
+ NS_ConvertUTF16toUTF8(aSessionId).get());
+ PERFORM_ON_CDM(LoadSession, aPromiseId, sessionType, aSessionId);
+}
+
+void WMFCDMProxy::UpdateSession(const nsAString& aSessionId,
+ PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RETURN_IF_SHUTDOWN();
+ EME_LOG("WMFCDMProxy::UpdateSession(this=%p, pid=%" PRIu32
+ "), sessionId=%s, responseLen=%zu",
+ this, aPromiseId, NS_ConvertUTF16toUTF8(aSessionId).get(),
+ aResponse.Length());
+ PERFORM_ON_CDM(UpdateSession, aPromiseId, aSessionId, aResponse);
+}
+
+void WMFCDMProxy::CloseSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RETURN_IF_SHUTDOWN();
+ EME_LOG("WMFCDMProxy::CloseSession(this=%p, pid=%" PRIu32 "), sessionId=%s",
+ this, aPromiseId, NS_ConvertUTF16toUTF8(aSessionId).get());
+ PERFORM_ON_CDM(CloseSession, aPromiseId, aSessionId);
+}
+
+void WMFCDMProxy::RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RETURN_IF_SHUTDOWN();
+ EME_LOG("WMFCDMProxy::RemoveSession(this=%p, pid=%" PRIu32 "), sessionId=%s",
+ this, aPromiseId, NS_ConvertUTF16toUTF8(aSessionId).get());
+ PERFORM_ON_CDM(RemoveSession, aPromiseId, aSessionId);
+}
+
+void WMFCDMProxy::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mIsShutdown);
+ if (mProxyCallback) {
+ mProxyCallback->Shutdown();
+ mProxyCallback = nullptr;
+ }
+ mIsShutdown = true;
+}
+
+void WMFCDMProxy::OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RETURN_IF_SHUTDOWN();
+ if (mKeys.IsNull()) {
+ return;
+ }
+ if (RefPtr<dom::MediaKeySession> session = mKeys->GetSession(aSessionId)) {
+ LOG("Notify key message for session Id=%s",
+ NS_ConvertUTF16toUTF8(aSessionId).get());
+ session->DispatchKeyMessage(aMessageType, aMessage);
+ }
+}
+
+void WMFCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RETURN_IF_SHUTDOWN();
+ if (mKeys.IsNull()) {
+ return;
+ }
+ if (RefPtr<dom::MediaKeySession> session = mKeys->GetSession(aSessionId)) {
+ LOG("Notify key statuses for session Id=%s",
+ NS_ConvertUTF16toUTF8(aSessionId).get());
+ session->DispatchKeyStatusesChange();
+ }
+}
+
+void WMFCDMProxy::OnExpirationChange(const nsAString& aSessionId,
+ UnixTime aExpiryTime) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RETURN_IF_SHUTDOWN();
+ if (mKeys.IsNull()) {
+ return;
+ }
+ if (RefPtr<dom::MediaKeySession> session = mKeys->GetSession(aSessionId)) {
+ LOG("Notify expiration for session Id=%s",
+ NS_ConvertUTF16toUTF8(aSessionId).get());
+ session->SetExpiration(static_cast<double>(aExpiryTime));
+ }
+}
+
+uint64_t WMFCDMProxy::GetCDMProxyId() const {
+ MOZ_DIAGNOSTIC_ASSERT(mCDM);
+ return mCDM->Id();
+}
+
+#undef LOG
+#undef RETURN_IF_SHUTDOWN
+#undef PERFORM_ON_CDM
+
+} // namespace mozilla
diff --git a/dom/media/eme/mediafoundation/WMFCDMProxy.h b/dom/media/eme/mediafoundation/WMFCDMProxy.h
new file mode 100644
index 0000000000..7766b73f21
--- /dev/null
+++ b/dom/media/eme/mediafoundation/WMFCDMProxy.h
@@ -0,0 +1,134 @@
+/* -*- 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 DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMPROXY_H_
+#define DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMPROXY_H_
+
+#include "mozilla/CDMProxy.h"
+#include "mozilla/CDMCaps.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/WMFCDMImpl.h"
+
+#include "nsString.h"
+
+namespace mozilla {
+
+class WMFCDMProxyCallback;
+
+class WMFCDMProxy : public CDMProxy {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WMFCDMProxy, override)
+
+ WMFCDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem,
+ const dom::MediaKeySystemConfiguration& aConfig);
+
+ // CDMProxy interface
+ void Init(PromiseId aPromiseId, const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin, const nsAString& aName) override;
+
+ void CreateSession(uint32_t aCreateSessionToken,
+ MediaKeySessionType aSessionType, PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData) override;
+
+ void LoadSession(PromiseId aPromiseId, dom::MediaKeySessionType aSessionType,
+ 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 QueryOutputProtectionStatus() override {}
+
+ void NotifyOutputProtectionStatus(
+ OutputProtectionCheckStatus aCheckStatus,
+ OutputProtectionCaptureStatus aCaptureStatus) override {}
+
+ void Shutdown() override;
+
+ void Terminated() 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,
+ const 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, ErrorResult&& aException,
+ const nsCString& aMsg) override {}
+
+ RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample) override {
+ return nullptr;
+ }
+ void OnDecrypted(uint32_t aId, DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) override {}
+
+ void ResolvePromise(PromiseId aId) override;
+ void RejectPromise(PromiseId aId, ErrorResult&& aException,
+ const nsCString& aReason) override;
+
+ void OnKeyStatusesChange(const nsAString& aSessionId) override;
+
+ void GetStatusForPolicy(PromiseId aPromiseId,
+ const nsAString& aMinHdcpVersion) override {}
+
+#ifdef DEBUG
+ bool IsOnOwnerThread() override {
+ return NS_GetCurrentThread() == mOwnerThread;
+ }
+#endif
+
+ WMFCDMProxy* AsWMFCDMProxy() override { return this; }
+
+ // Can only be called after initialization succeeded.
+ uint64_t GetCDMProxyId() const;
+
+ private:
+ virtual ~WMFCDMProxy();
+
+ template <typename T>
+ void ResolvePromiseWithResult(PromiseId aId, const T& aResult) {}
+ void RejectPromiseOnMainThread(PromiseId aId,
+ CopyableErrorResult&& aException,
+ const nsCString& aReason);
+ // Reject promise with an InvalidStateError and the given message.
+ void RejectPromiseWithStateError(PromiseId aId, const nsCString& aReason);
+
+ RefPtr<WMFCDMImpl> mCDM;
+
+ const dom::MediaKeySystemConfiguration mConfig;
+
+ RefPtr<WMFCDMProxyCallback> mProxyCallback;
+
+ // It can only be used on the main thread.
+ bool mIsShutdown = false;
+};
+
+} // namespace mozilla
+
+#endif // DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMPROXY_H_
diff --git a/dom/media/eme/mediafoundation/WMFCDMProxyCallback.cpp b/dom/media/eme/mediafoundation/WMFCDMProxyCallback.cpp
new file mode 100644
index 0000000000..6464cec4c7
--- /dev/null
+++ b/dom/media/eme/mediafoundation/WMFCDMProxyCallback.cpp
@@ -0,0 +1,72 @@
+/* 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 "WMFCDMProxyCallback.h"
+
+#include "mozilla/WMFCDMProxy.h"
+
+#define RETURN_IF_NULL(proxy) \
+ if (!proxy) { \
+ return; \
+ }
+
+namespace mozilla {
+
+WMFCDMProxyCallback::WMFCDMProxyCallback(WMFCDMProxy* aProxy) : mProxy(aProxy) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mProxy);
+}
+
+WMFCDMProxyCallback::~WMFCDMProxyCallback() { MOZ_ASSERT(!mProxy); }
+
+void WMFCDMProxyCallback::OnSessionMessage(const MFCDMKeyMessage& aMessage) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "WMFCDMProxyCallback::OnSessionMessage",
+ [self = RefPtr{this}, this, message = aMessage]() {
+ RETURN_IF_NULL(mProxy);
+ mProxy->OnSessionMessage(message.sessionId(), message.type(),
+ std::move(message.message()));
+ }));
+}
+
+void WMFCDMProxyCallback::OnSessionKeyStatusesChange(
+ const MFCDMKeyStatusChange& aKeyStatuses) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "WMFCDMProxyCallback::OnSessionKeyStatusesChange",
+ [self = RefPtr{this}, this, keyStatuses = aKeyStatuses]() {
+ RETURN_IF_NULL(mProxy);
+ bool keyStatusesChange = false;
+ {
+ auto caps = mProxy->Capabilites().Lock();
+ for (const auto& keyInfo : keyStatuses.keyInfo()) {
+ keyStatusesChange |= caps->SetKeyStatus(
+ keyInfo.keyId(), keyStatuses.sessionId(),
+ dom::Optional<dom::MediaKeyStatus>(keyInfo.status()));
+ }
+ }
+ if (keyStatusesChange) {
+ mProxy->OnKeyStatusesChange(keyStatuses.sessionId());
+ }
+ }));
+}
+
+void WMFCDMProxyCallback::OnSessionKeyExpiration(
+ const MFCDMKeyExpiration& aExpiration) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "WMFCDMProxyCallback::OnSessionKeyExpiration",
+ [self = RefPtr{this}, this, expiration = aExpiration]() {
+ RETURN_IF_NULL(mProxy);
+ mProxy->OnExpirationChange(
+ expiration.sessionId(),
+ expiration.expiredTimeMilliSecondsSinceEpoch());
+ }));
+}
+
+void WMFCDMProxyCallback::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mProxy = nullptr;
+}
+
+#undef RETURN_IF_NULL
+} // namespace mozilla
diff --git a/dom/media/eme/mediafoundation/WMFCDMProxyCallback.h b/dom/media/eme/mediafoundation/WMFCDMProxyCallback.h
new file mode 100644
index 0000000000..6e76f63ccc
--- /dev/null
+++ b/dom/media/eme/mediafoundation/WMFCDMProxyCallback.h
@@ -0,0 +1,38 @@
+/* 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 DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMPROXYCALLBACK_H_
+#define DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMPROXYCALLBACK_H_
+
+#include "mozilla/PMFCDM.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+class WMFCDMProxy;
+
+// This class is used to notify CDM related events called from MFCDMChild, and
+// it will forward the relative calls to WMFCDMProxy on the main thread.
+class WMFCDMProxyCallback final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WMFCDMProxyCallback);
+
+ explicit WMFCDMProxyCallback(WMFCDMProxy* aProxy);
+
+ void OnSessionMessage(const MFCDMKeyMessage& aMessage);
+
+ void OnSessionKeyStatusesChange(const MFCDMKeyStatusChange& aKeyStatuses);
+
+ void OnSessionKeyExpiration(const MFCDMKeyExpiration& aExpiration);
+
+ void Shutdown();
+
+ private:
+ ~WMFCDMProxyCallback();
+ RefPtr<WMFCDMProxy> mProxy;
+};
+
+} // namespace mozilla
+
+#endif // DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMPROXYCALLBACK_H_
diff --git a/dom/media/eme/mediafoundation/moz.build b/dom/media/eme/mediafoundation/moz.build
new file mode 100644
index 0000000000..8c54b381ff
--- /dev/null
+++ b/dom/media/eme/mediafoundation/moz.build
@@ -0,0 +1,21 @@
+# -*- 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 += [
+ "WMFCDMImpl.h",
+ "WMFCDMProxy.h",
+ "WMFCDMProxyCallback.h",
+]
+
+UNIFIED_SOURCES += [
+ "WMFCDMImpl.cpp",
+ "WMFCDMProxy.cpp",
+ "WMFCDMProxyCallback.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/media/eme/moz.build b/dom/media/eme/moz.build
new file mode 100644
index 0000000000..76059a2790
--- /dev/null
+++ b/dom/media/eme/moz.build
@@ -0,0 +1,54 @@
+# -*- 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 += [
+ "KeySystemNames.h",
+ "MediaEncryptedEvent.h",
+ "MediaKeyError.h",
+ "MediaKeyMessageEvent.h",
+ "MediaKeys.h",
+ "MediaKeySession.h",
+ "MediaKeyStatusMap.h",
+ "MediaKeySystemAccess.h",
+ "MediaKeySystemAccessManager.h",
+ "MediaKeySystemAccessPermissionRequest.h",
+]
+
+EXPORTS.mozilla += [
+ "CDMCaps.h",
+ "CDMProxy.h",
+ "DecryptorProxyCallback.h",
+ "DetailedPromise.h",
+ "EMEUtils.h",
+ "KeySystemConfig.h",
+]
+
+UNIFIED_SOURCES += [
+ "CDMCaps.cpp",
+ "DetailedPromise.cpp",
+ "EMEUtils.cpp",
+ "KeySystemConfig.cpp",
+ "MediaEncryptedEvent.cpp",
+ "MediaKeyError.cpp",
+ "MediaKeyMessageEvent.cpp",
+ "MediaKeys.cpp",
+ "MediaKeySession.cpp",
+ "MediaKeyStatusMap.cpp",
+ "MediaKeySystemAccess.cpp",
+ "MediaKeySystemAccessManager.cpp",
+ "MediaKeySystemAccessPermissionRequest.cpp",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ DIRS += ["mediadrm"]
+ LOCAL_INCLUDES += ["/dom/media/platforms/android"]
+
+if CONFIG["MOZ_WMF_CDM"]:
+ DIRS += ["mediafoundation"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"