/* -*- 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 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& session : mKeySessions.Values()) { // XXX Could the RefPtr still be moved here? keySessions.InsertOrUpdate(session->GetSessionId(), RefPtr{session}); } for (const RefPtr& 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 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 selfReference = this; for (const RefPtr& 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 aGivenProto) { return MediaKeys_Binding::Wrap(aCx, this, aGivenProto); } void MediaKeys::GetKeySystem(nsString& aOutKeySystem) const { aOutKeySystem.Assign(mKeySystem); } already_AddRefed MediaKeys::SetServerCertificate( const ArrayBufferViewOrArrayBuffer& aCert, ErrorResult& aRv) { RefPtr 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 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 MediaKeys::MakePromise( ErrorResult& aRv, const nsACString& aName) { nsCOMPtr 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& 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 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 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 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 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 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 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 mMediaKeys; }; already_AddRefed MediaKeys::CreateCDMProxy() { EME_LOG("MediaKeys[%p]::CreateCDMProxy()", this); RefPtr 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 MediaKeys::Init(ErrorResult& aRv) { EME_LOG("MediaKeys[%p]::Init()", this); RefPtr promise(MakePromise(aRv, "MediaKeys::Init()"_ns)); if (aRv.Failed()) { return nullptr; } // Determine principal (at creation time) of the MediaKeys object. nsCOMPtr 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 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 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 promise(RetrievePromise(aId)); if (!promise) { return; } RefPtr 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 MediaKeys::CreateSession( MediaKeySessionType aSessionType, ErrorResult& aRv) { EME_LOG("MediaKeys[%p]::CreateSession(aSessionType=%" PRIu8 ")", this, static_cast(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 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(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 MediaKeys::GetSession( const nsAString& aSessionId) { RefPtr session; mKeySessions.Get(aSessionId, getter_AddRefs(session)); return session.forget(); } already_AddRefed MediaKeys::GetPendingSession( uint32_t aToken) { EME_LOG("MediaKeys[%p]::GetPendingSession(aToken=%" PRIu32 ")", this, aToken); RefPtr 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 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 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 MediaKeys::GetStatusForPolicy( const MediaKeysPolicy& aPolicy, ErrorResult& aRv) { RefPtr 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 promise(RetrievePromise(aId)); if (!promise) { return; } RefPtr keys(this); EME_LOG( "MediaKeys[%p]::ResolvePromiseWithKeyStatus() resolve promise id=%" PRIu32 ", keystatus=%" PRIu8, this, aId, static_cast(aMediaKeyStatus)); promise->MaybeResolve(aMediaKeyStatus); } } // namespace mozilla::dom