MediaKeys.cpp (31069B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/MediaKeys.h" 8 9 #include "ChromiumCDMProxy.h" 10 #include "GMPCrashHelper.h" 11 #include "mozilla/EMEUtils.h" 12 #include "mozilla/JSONStringWriteFuncs.h" 13 #include "mozilla/dom/DOMException.h" 14 #include "mozilla/dom/Document.h" 15 #include "mozilla/dom/HTMLMediaElement.h" 16 #include "mozilla/dom/MediaKeyError.h" 17 #include "mozilla/dom/MediaKeyMessageEvent.h" 18 #include "mozilla/dom/MediaKeySession.h" 19 #include "mozilla/dom/MediaKeySessionBinding.h" 20 #include "mozilla/dom/MediaKeyStatusMap.h" 21 #include "mozilla/dom/MediaKeySystemAccess.h" 22 #include "mozilla/dom/MediaKeysBinding.h" 23 #include "mozilla/dom/UnionTypes.h" 24 #include "mozilla/dom/WindowContext.h" 25 #include "mozilla/dom/WindowGlobalChild.h" 26 #include "nsContentTypeParser.h" 27 #include "nsContentUtils.h" 28 #include "nsIScriptObjectPrincipal.h" 29 #include "nsPrintfCString.h" 30 #include "nsServiceManagerUtils.h" 31 32 #ifdef MOZ_WIDGET_ANDROID 33 # include "AndroidDecoderModule.h" 34 # include "mozilla/MediaDrmCDMProxy.h" 35 # include "mozilla/RemoteCDMChild.h" 36 # include "mozilla/RemoteMediaManagerChild.h" 37 # include "mozilla/StaticPrefs_media.h" 38 #endif 39 #ifdef MOZ_WMF_CDM 40 # include "mozilla/WMFCDMProxy.h" 41 #endif 42 43 namespace mozilla::dom { 44 45 // We don't use NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE because we need to 46 // disconnect our MediaKeys instances from the inner window (mparent) before 47 // we unlink it. 48 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(MediaKeys) 49 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaKeys) 50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement) 51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) 52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mKeySessions) 53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises) 54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingSessions) 55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 56 57 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaKeys) 58 NS_IMPL_CYCLE_COLLECTION_UNLINK(mElement) 59 tmp->DisconnectInnerWindow(); 60 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) 61 NS_IMPL_CYCLE_COLLECTION_UNLINK(mKeySessions) 62 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromises) 63 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingSessions) 64 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 65 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR 66 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 67 68 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeys) 69 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeys) 70 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeys) 71 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 72 NS_INTERFACE_MAP_ENTRY(nsISupports) 73 NS_INTERFACE_MAP_ENTRY(nsIObserver) 74 NS_INTERFACE_MAP_END 75 76 MediaKeys::MediaKeys(nsPIDOMWindowInner* aParent, const nsAString& aKeySystem, 77 const MediaKeySystemConfiguration& aConfig) 78 : mParent(aParent), 79 mKeySystem(aKeySystem), 80 mCreatePromiseId(0), 81 mConfig(aConfig) { 82 EME_LOG("MediaKeys[%p] constructed keySystem=%s", this, 83 NS_ConvertUTF16toUTF8(mKeySystem).get()); 84 } 85 86 MediaKeys::~MediaKeys() { 87 MOZ_ASSERT(NS_IsMainThread()); 88 89 DisconnectInnerWindow(); 90 Shutdown(); 91 EME_LOG("MediaKeys[%p] destroyed", this); 92 } 93 94 NS_IMETHODIMP MediaKeys::Observe(nsISupports* aSubject, const char* aTopic, 95 const char16_t* aData) { 96 MOZ_ASSERT(NS_IsMainThread()); 97 MOZ_ASSERT(!strcmp(aTopic, kMediaKeysResponseTopic), 98 "Should only listen for responses to MediaKey requests"); 99 EME_LOG("MediaKeys[%p] observing message with aTopic=%s aData=%s", this, 100 aTopic, NS_ConvertUTF16toUTF8(aData).get()); 101 if (!strcmp(aTopic, kMediaKeysResponseTopic)) { 102 if (!mProxy) { 103 // This may happen if we're notified during shutdown or startup. If this 104 // is happening outside of those scenarios there's a bug. 105 EME_LOG( 106 "MediaKeys[%p] can't notify CDM of observed message as mProxy is " 107 "unset", 108 this); 109 return NS_OK; 110 } 111 112 if (u"capture-possible"_ns.Equals(aData)) { 113 mProxy->NotifyOutputProtectionStatus( 114 CDMProxy::OutputProtectionCheckStatus::CheckSuccessful, 115 CDMProxy::OutputProtectionCaptureStatus::CapturePossilbe); 116 } else if (u"capture-not-possible"_ns.Equals(aData)) { 117 mProxy->NotifyOutputProtectionStatus( 118 CDMProxy::OutputProtectionCheckStatus::CheckSuccessful, 119 CDMProxy::OutputProtectionCaptureStatus::CaptureNotPossible); 120 } else { 121 MOZ_ASSERT_UNREACHABLE("No code paths should lead to the failure case"); 122 // This should be unreachable, but gracefully handle in case. 123 mProxy->NotifyOutputProtectionStatus( 124 CDMProxy::OutputProtectionCheckStatus::CheckFailed, 125 CDMProxy::OutputProtectionCaptureStatus::Unused); 126 } 127 } 128 return NS_OK; 129 } 130 131 void MediaKeys::ConnectInnerWindow() { 132 MOZ_ASSERT(NS_IsMainThread()); 133 134 nsCOMPtr<nsPIDOMWindowInner> innerWindowParent = GetParentObject(); 135 MOZ_ASSERT(innerWindowParent, 136 "We should only be connecting when we have an inner window!"); 137 innerWindowParent->AddMediaKeysInstance(this); 138 } 139 140 void MediaKeys::DisconnectInnerWindow() { 141 MOZ_ASSERT(NS_IsMainThread()); 142 143 if (!GetParentObject()) { 144 // We don't have a parent. We've been cycle collected, or the window 145 // already notified us of its destruction and we cleared the ref. 146 return; 147 } 148 149 GetParentObject()->RemoveMediaKeysInstance(this); 150 } 151 152 void MediaKeys::OnInnerWindowDestroy() { 153 MOZ_ASSERT(NS_IsMainThread()); 154 155 EME_LOG("MediaKeys[%p] OnInnerWindowDestroy()", this); 156 157 // The InnerWindow should clear its reference to this object after this call, 158 // so we don't need to explicitly call DisconnectInnerWindow before nulling. 159 mParent = nullptr; 160 161 // Don't call shutdown directly because (at time of writing) mProxy can 162 // spin the event loop when it's shutdown. This can change the world state 163 // in the middle of window destruction, which we do not want. 164 GetMainThreadSerialEventTarget()->Dispatch( 165 NewRunnableMethod("MediaKeys::Shutdown", this, &MediaKeys::Shutdown)); 166 } 167 168 void MediaKeys::Terminated() { 169 EME_LOG("MediaKeys[%p] CDM crashed unexpectedly", this); 170 171 KeySessionHashMap keySessions; 172 // Remove entries during iteration will screw it. Make a copy first. 173 for (const RefPtr<MediaKeySession>& session : mKeySessions.Values()) { 174 // XXX Could the RefPtr still be moved here? 175 keySessions.InsertOrUpdate(session->GetSessionId(), RefPtr{session}); 176 } 177 for (const RefPtr<MediaKeySession>& session : keySessions.Values()) { 178 session->OnClosed(MediaKeySessionClosedReason::Internal_error); 179 } 180 keySessions.Clear(); 181 MOZ_ASSERT(mKeySessions.Count() == 0); 182 183 // Notify the element about that CDM has terminated. 184 if (mElement) { 185 mElement->DecodeError(NS_ERROR_DOM_MEDIA_CDM_ERR); 186 } 187 188 Shutdown(); 189 } 190 191 void MediaKeys::Shutdown() { 192 // Hold a self reference to keep us alive after we clear the self reference 193 // for each promise. This ensures we stay alive until we're done shutting 194 // down. 195 RefPtr<MediaKeys> selfReference = this; 196 197 EME_LOG("MediaKeys[%p]::Shutdown()", this); 198 if (mProxy) { 199 RefPtr<CDMProxy> proxy = mProxy; 200 proxy->Shutdown(); 201 mProxy = nullptr; 202 } 203 204 nsCOMPtr<nsIObserverService> observerService = 205 mozilla::services::GetObserverService(); 206 if (observerService && mObserverAdded) { 207 observerService->RemoveObserver(this, kMediaKeysResponseTopic); 208 } 209 210 for (const RefPtr<dom::DetailedPromise>& promise : mPromises.Values()) { 211 promise->MaybeRejectWithInvalidStateError( 212 "Promise still outstanding at MediaKeys shutdown"); 213 Release(); 214 } 215 mPromises.Clear(); 216 } 217 218 nsPIDOMWindowInner* MediaKeys::GetParentObject() const { return mParent; } 219 220 JSObject* MediaKeys::WrapObject(JSContext* aCx, 221 JS::Handle<JSObject*> aGivenProto) { 222 return MediaKeys_Binding::Wrap(aCx, this, aGivenProto); 223 } 224 225 void MediaKeys::GetKeySystem(nsString& aOutKeySystem) const { 226 aOutKeySystem.Assign(mKeySystem); 227 } 228 229 already_AddRefed<DetailedPromise> MediaKeys::SetServerCertificate( 230 const ArrayBufferViewOrArrayBuffer& aCert, ErrorResult& aRv) { 231 RefPtr<DetailedPromise> promise( 232 MakePromise(aRv, "MediaKeys.setServerCertificate"_ns)); 233 if (aRv.Failed()) { 234 return nullptr; 235 } 236 237 if (!mProxy) { 238 NS_WARNING("Tried to use a MediaKeys without a CDM"); 239 promise->MaybeRejectWithInvalidStateError( 240 "Null CDM in MediaKeys.setServerCertificate()"); 241 return promise.forget(); 242 } 243 244 nsTArray<uint8_t> data; 245 CopyArrayBufferViewOrArrayBufferData(aCert, data); 246 if (data.IsEmpty()) { 247 promise->MaybeRejectWithTypeError( 248 "Empty certificate passed to MediaKeys.setServerCertificate()"); 249 return promise.forget(); 250 } 251 252 mProxy->SetServerCertificate(StorePromise(promise), data); 253 return promise.forget(); 254 } 255 256 already_AddRefed<DetailedPromise> MediaKeys::MakePromise( 257 ErrorResult& aRv, const nsACString& aName) { 258 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject()); 259 if (!global) { 260 NS_WARNING("Passed non-global to MediaKeys ctor!"); 261 aRv.Throw(NS_ERROR_UNEXPECTED); 262 return nullptr; 263 } 264 return DetailedPromise::Create(global, aRv, aName); 265 } 266 267 PromiseId MediaKeys::StorePromise(DetailedPromise* aPromise) { 268 static uint32_t sEMEPromiseCount = 1; 269 MOZ_ASSERT(aPromise); 270 uint32_t id = sEMEPromiseCount++; 271 272 EME_LOG("MediaKeys[%p]::StorePromise() id=%" PRIu32, this, id); 273 274 // Keep MediaKeys alive for the lifetime of its promises. Any still-pending 275 // promises are rejected in Shutdown(). 276 EME_LOG("MediaKeys[%p]::StorePromise() calling AddRef()", this); 277 AddRef(); 278 279 #ifdef DEBUG 280 // We should not have already stored this promise! 281 for (const RefPtr<dom::DetailedPromise>& promise : mPromises.Values()) { 282 MOZ_ASSERT(promise != aPromise); 283 } 284 #endif 285 286 mPromises.InsertOrUpdate(id, RefPtr{aPromise}); 287 return id; 288 } 289 290 void MediaKeys::ConnectPendingPromiseIdWithToken(PromiseId aId, 291 uint32_t aToken) { 292 // Should only be called from MediaKeySession::GenerateRequest. 293 mPromiseIdToken.InsertOrUpdate(aId, aToken); 294 EME_LOG( 295 "MediaKeys[%p]::ConnectPendingPromiseIdWithToken() id=%u => token(%u)", 296 this, aId, aToken); 297 } 298 299 already_AddRefed<DetailedPromise> MediaKeys::RetrievePromise(PromiseId aId) { 300 EME_LOG("MediaKeys[%p]::RetrievePromise(aId=%" PRIu32 ")", this, aId); 301 if (!mPromises.Contains(aId)) { 302 EME_LOG("MediaKeys[%p]::RetrievePromise(aId=%" PRIu32 303 ") tried to retrieve non-existent promise!", 304 this, aId); 305 NS_WARNING(nsPrintfCString( 306 "Tried to retrieve a non-existent promise id=%" PRIu32, aId) 307 .get()); 308 return nullptr; 309 } 310 RefPtr<DetailedPromise> promise; 311 mPromises.Remove(aId, getter_AddRefs(promise)); 312 EME_LOG("MediaKeys[%p]::RetrievePromise(aId=%" PRIu32 ") calling Release()", 313 this, aId); 314 Release(); 315 return promise.forget(); 316 } 317 318 void MediaKeys::RejectPromise(PromiseId aId, ErrorResult&& aException, 319 const nsCString& aReason) { 320 uint32_t errorCodeAsInt = aException.ErrorCodeAsInt(); 321 EME_LOG("MediaKeys[%p]::RejectPromise(%" PRIu32 ", 0x%" PRIx32 ")", this, aId, 322 errorCodeAsInt); 323 324 RefPtr<DetailedPromise> promise(RetrievePromise(aId)); 325 if (!promise) { 326 EME_LOG("MediaKeys[%p]::RejectPromise(%" PRIu32 ", 0x%" PRIx32 327 ") couldn't retrieve promise! Bailing!", 328 this, aId, errorCodeAsInt); 329 return; 330 } 331 332 // This promise could be a createSession or loadSession promise, 333 // so we might have a pending session waiting to be resolved into 334 // the promise on success. We've been directed to reject to promise, 335 // so we can throw away the corresponding session object. 336 uint32_t token = 0; 337 if (mPromiseIdToken.Get(aId, &token)) { 338 MOZ_ASSERT(mPendingSessions.Contains(token)); 339 mPendingSessions.Remove(token); 340 mPromiseIdToken.Remove(aId); 341 } 342 343 MOZ_ASSERT(aException.Failed()); 344 promise->MaybeReject(std::move(aException), aReason); 345 346 if (mCreatePromiseId == aId) { 347 // Note: This will probably destroy the MediaKeys object! 348 EME_LOG("MediaKeys[%p]::RejectPromise(%" PRIu32 ", 0x%" PRIx32 349 ") calling Release()", 350 this, aId, errorCodeAsInt); 351 Release(); 352 } 353 } 354 355 void MediaKeys::OnSessionIdReady(MediaKeySession* aSession) { 356 if (!aSession) { 357 NS_WARNING("Invalid MediaKeySession passed to OnSessionIdReady()"); 358 return; 359 } 360 if (mKeySessions.Contains(aSession->GetSessionId())) { 361 NS_WARNING("MediaKeySession's made ready multiple times!"); 362 return; 363 } 364 if (mPendingSessions.Contains(aSession->Token())) { 365 NS_WARNING( 366 "MediaKeySession made ready when it wasn't waiting to be ready!"); 367 return; 368 } 369 if (aSession->GetSessionId().IsEmpty()) { 370 NS_WARNING( 371 "MediaKeySession with invalid sessionId passed to OnSessionIdReady()"); 372 return; 373 } 374 mKeySessions.InsertOrUpdate(aSession->GetSessionId(), RefPtr{aSession}); 375 } 376 377 void MediaKeys::ResolvePromise(PromiseId aId) { 378 EME_LOG("MediaKeys[%p]::ResolvePromise(%" PRIu32 ")", this, aId); 379 380 RefPtr<DetailedPromise> promise(RetrievePromise(aId)); 381 MOZ_ASSERT(!mPromises.Contains(aId)); 382 if (!promise) { 383 return; 384 } 385 386 uint32_t token = 0; 387 if (!mPromiseIdToken.Get(aId, &token)) { 388 promise->MaybeResolveWithUndefined(); 389 return; 390 } else if (!mPendingSessions.Contains(token)) { 391 // Pending session for CreateSession() should be removed when sessionId 392 // is ready. 393 promise->MaybeResolveWithUndefined(); 394 mPromiseIdToken.Remove(aId); 395 return; 396 } 397 mPromiseIdToken.Remove(aId); 398 399 // We should only resolve LoadSession calls via this path, 400 // not CreateSession() promises. 401 RefPtr<MediaKeySession> session; 402 mPendingSessions.Remove(token, getter_AddRefs(session)); 403 if (!session || session->GetSessionId().IsEmpty()) { 404 NS_WARNING("Received activation for non-existent session!"); 405 promise->MaybeRejectWithInvalidAccessError( 406 "CDM LoadSession() returned a different session ID than requested"); 407 return; 408 } 409 mKeySessions.InsertOrUpdate(session->GetSessionId(), RefPtr{session}); 410 promise->MaybeResolve(session); 411 } 412 413 class MediaKeysGMPCrashHelper : public GMPCrashHelper { 414 public: 415 explicit MediaKeysGMPCrashHelper(MediaKeys* aMediaKeys) 416 : mMediaKeys(aMediaKeys) { 417 MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe. 418 } 419 already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override { 420 MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe. 421 EME_LOG("MediaKeysGMPCrashHelper::GetPluginCrashedEventTarget()"); 422 return (mMediaKeys && mMediaKeys->GetParentObject()) 423 ? do_AddRef(mMediaKeys->GetParentObject()) 424 : nullptr; 425 } 426 427 private: 428 WeakPtr<MediaKeys> mMediaKeys; 429 }; 430 431 already_AddRefed<CDMProxy> MediaKeys::CreateCDMProxy() { 432 const bool isHardwareDecryptionSupported = 433 IsHardwareDecryptionSupported(mConfig) || 434 DoesKeySystemSupportHardwareDecryption(mKeySystem); 435 EME_LOG("MediaKeys[%p]::CreateCDMProxy(), isHardwareDecryptionSupported=%d", 436 this, isHardwareDecryptionSupported); 437 RefPtr<CDMProxy> proxy; 438 #ifdef MOZ_WIDGET_ANDROID 439 if (IsWidevineKeySystem(mKeySystem)) { 440 if (AndroidDecoderModule::IsJavaDecoderModuleAllowed()) { 441 proxy = new MediaDrmCDMProxy( 442 this, mKeySystem, 443 mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required, 444 mConfig.mPersistentState == MediaKeysRequirement::Required); 445 } else { 446 proxy = RemoteMediaManagerChild::CreateCDM( 447 RemoteMediaIn::RddProcess, this, mKeySystem, 448 mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required, 449 mConfig.mPersistentState == MediaKeysRequirement::Required); 450 } 451 } else 452 #endif 453 #ifdef MOZ_WMF_CDM 454 if (IsPlayReadyKeySystemAndSupported(mKeySystem) || 455 IsWidevineExperimentKeySystemAndSupported(mKeySystem) || 456 (IsWidevineKeySystem(mKeySystem) && isHardwareDecryptionSupported) || 457 IsWMFClearKeySystemAndSupported(mKeySystem)) { 458 proxy = new WMFCDMProxy(this, mKeySystem, mConfig); 459 } else 460 #endif 461 { 462 proxy = new ChromiumCDMProxy( 463 this, mKeySystem, new MediaKeysGMPCrashHelper(this), 464 mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required, 465 mConfig.mPersistentState == MediaKeysRequirement::Required); 466 } 467 return proxy.forget(); 468 } 469 470 already_AddRefed<DetailedPromise> MediaKeys::Init(ErrorResult& aRv) { 471 EME_LOG("MediaKeys[%p]::Init()", this); 472 RefPtr<DetailedPromise> promise(MakePromise(aRv, "MediaKeys::Init()"_ns)); 473 if (aRv.Failed()) { 474 return nullptr; 475 } 476 477 // Determine principal (at creation time) of the MediaKeys object. 478 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetParentObject()); 479 if (!sop) { 480 promise->MaybeRejectWithInvalidStateError( 481 "Couldn't get script principal in MediaKeys::Init"); 482 return promise.forget(); 483 } 484 mPrincipal = sop->GetPrincipal(); 485 486 // Begin figuring out the top level principal. 487 nsCOMPtr<nsPIDOMWindowInner> window = GetParentObject(); 488 489 // If we're in a top level document, getting the top level principal is easy. 490 // However, we're not in a top level doc this becomes more complicated. If 491 // we're not top level we need to get the top level principal, this can be 492 // done by reading the principal of the load info, which we can get of a 493 // document's channel. 494 // 495 // There is an edge case we need to watch out for here where this code can be 496 // run in an about:blank document before it has done its async load. In this 497 // case the document will not yet have a load info. We address this below by 498 // walking up a level in the window context chain. See 499 // https://bugzilla.mozilla.org/show_bug.cgi?id=1675360 500 // for more info. 501 Document* document = window->GetExtantDoc(); 502 if (!document) { 503 NS_WARNING("Failed to get document when creating MediaKeys"); 504 promise->MaybeRejectWithInvalidStateError( 505 "Couldn't get document in MediaKeys::Init"); 506 return promise.forget(); 507 } 508 509 WindowGlobalChild* windowGlobalChild = window->GetWindowGlobalChild(); 510 if (!windowGlobalChild) { 511 NS_WARNING("Failed to get window global child when creating MediaKeys"); 512 promise->MaybeRejectWithInvalidStateError( 513 "Couldn't get window global child in MediaKeys::Init"); 514 return promise.forget(); 515 } 516 517 if (windowGlobalChild->SameOriginWithTop()) { 518 // We're in the same origin as the top window context, so our principal 519 // is also the top principal. 520 mTopLevelPrincipal = mPrincipal; 521 } else { 522 // We have a different origin than the top doc, try and find the top level 523 // principal by looking it up via load info, which we read off a channel. 524 nsIChannel* channel = document->GetChannel(); 525 526 WindowContext* windowContext = document->GetWindowContext(); 527 if (!windowContext) { 528 NS_WARNING("Failed to get window context when creating MediaKeys"); 529 promise->MaybeRejectWithInvalidStateError( 530 "Couldn't get window context in MediaKeys::Init"); 531 return promise.forget(); 532 } 533 while (!channel) { 534 // We don't have a channel, this can happen if we're in an about:blank 535 // page that hasn't yet had its async load performed. Try and get 536 // the channel from our parent doc. We should be able to do this because 537 // an about:blank is considered the same origin as its parent. We do this 538 // recursively to cover pages do silly things like nesting blank iframes 539 // and not waiting for loads. 540 541 // Move our window context up a level. 542 windowContext = windowContext->GetParentWindowContext(); 543 if (!windowContext || !windowContext->GetExtantDoc()) { 544 NS_WARNING( 545 "Failed to get parent window context's document when creating " 546 "MediaKeys"); 547 promise->MaybeRejectWithInvalidStateError( 548 "Couldn't get parent window context's document in " 549 "MediaKeys::Init (likely due to an nested about about:blank frame " 550 "that hasn't loaded yet)"); 551 return promise.forget(); 552 } 553 554 Document* parentDoc = windowContext->GetExtantDoc(); 555 channel = parentDoc->GetChannel(); 556 } 557 558 MOZ_RELEASE_ASSERT( 559 channel, "Should either have a channel or should have returned by now"); 560 561 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); 562 MOZ_RELEASE_ASSERT(loadInfo, "Channels should always have LoadInfo"); 563 mTopLevelPrincipal = loadInfo->GetTopLevelPrincipal(); 564 if (!mTopLevelPrincipal) { 565 NS_WARNING("Failed to get top level principal when creating MediaKeys"); 566 promise->MaybeRejectWithInvalidStateError( 567 "Couldn't get top level principal in MediaKeys::Init"); 568 return promise.forget(); 569 } 570 } 571 572 // We should have figured out our top level principal. 573 if (!mPrincipal || !mTopLevelPrincipal) { 574 NS_WARNING("Failed to get principals when creating MediaKeys"); 575 promise->MaybeRejectWithInvalidStateError( 576 "Couldn't get principal(s) in MediaKeys::Init"); 577 return promise.forget(); 578 } 579 580 nsAutoCString origin; 581 nsresult rv = mPrincipal->GetOrigin(origin); 582 if (NS_FAILED(rv)) { 583 promise->MaybeRejectWithInvalidStateError( 584 "Couldn't get principal origin string in MediaKeys::Init"); 585 return promise.forget(); 586 } 587 nsAutoCString topLevelOrigin; 588 rv = mTopLevelPrincipal->GetOrigin(topLevelOrigin); 589 if (NS_FAILED(rv)) { 590 promise->MaybeRejectWithInvalidStateError( 591 "Couldn't get top-level principal origin string in MediaKeys::Init"); 592 return promise.forget(); 593 } 594 595 EME_LOG("MediaKeys[%p]::Create() (%s, %s)", this, origin.get(), 596 topLevelOrigin.get()); 597 598 mProxy = CreateCDMProxy(); 599 600 // The CDMProxy's initialization is asynchronous. The MediaKeys is 601 // refcounted, and its instance is returned to JS by promise once 602 // it's been initialized. No external refs exist to the MediaKeys while 603 // we're waiting for the promise to be resolved, so we must hold a 604 // reference to the new MediaKeys object until it's been created, 605 // or its creation has failed. Store the id of the promise returned 606 // here, and hold a self-reference until that promise is resolved or 607 // rejected. 608 MOZ_ASSERT(!mCreatePromiseId, "Should only be created once!"); 609 mCreatePromiseId = StorePromise(promise); 610 EME_LOG("MediaKeys[%p]::Init() calling AddRef()", this); 611 AddRef(); 612 mProxy->Init(mCreatePromiseId, NS_ConvertUTF8toUTF16(origin), 613 NS_ConvertUTF8toUTF16(topLevelOrigin), 614 KeySystemToProxyName(mKeySystem)); 615 616 ConnectInnerWindow(); 617 618 return promise.forget(); 619 } 620 621 void MediaKeys::OnCDMCreated(PromiseId aId, const uint32_t aPluginId) { 622 EME_LOG("MediaKeys[%p]::OnCDMCreated(aId=%" PRIu32 ", aPluginId=%" PRIu32 ")", 623 this, aId, aPluginId); 624 RefPtr<DetailedPromise> promise(RetrievePromise(aId)); 625 if (!promise) { 626 return; 627 } 628 RefPtr<MediaKeys> keys(this); 629 630 promise->MaybeResolve(keys); 631 if (mCreatePromiseId == aId) { 632 EME_LOG("MediaKeys[%p]::OnCDMCreated(aId=%" PRIu32 ", aPluginId=%" PRIu32 633 ") calling Release()", 634 this, aId, aPluginId); 635 Release(); 636 } 637 638 MediaKeySystemAccess::NotifyObservers(mParent, mKeySystem, 639 MediaKeySystemStatus::Cdm_created); 640 } 641 642 static bool IsSessionTypeSupported(const MediaKeySessionType aSessionType, 643 const MediaKeySystemConfiguration& aConfig) { 644 if (aSessionType == MediaKeySessionType::Temporary) { 645 // Temporary is always supported. 646 return true; 647 } 648 if (!aConfig.mSessionTypes.WasPassed()) { 649 // No other session types supported. 650 return false; 651 } 652 return aConfig.mSessionTypes.Value().Contains(ToString(aSessionType)); 653 } 654 655 already_AddRefed<MediaKeySession> MediaKeys::CreateSession( 656 MediaKeySessionType aSessionType, ErrorResult& aRv) { 657 EME_LOG("MediaKeys[%p]::CreateSession(aSessionType=%" PRIu8 ")", this, 658 static_cast<uint8_t>(aSessionType)); 659 if (!IsSessionTypeSupported(aSessionType, mConfig)) { 660 EME_LOG("MediaKeys[%p]::CreateSession() failed, unsupported session type", 661 this); 662 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); 663 return nullptr; 664 } 665 666 if (!mProxy) { 667 NS_WARNING("Tried to use a MediaKeys which lost its CDM"); 668 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 669 return nullptr; 670 } 671 672 EME_LOG("MediaKeys[%p] Creating session", this); 673 674 const bool isHardwareDecryption = 675 IsHardwareDecryptionSupported(mConfig) || 676 DoesKeySystemSupportHardwareDecryption(mKeySystem); 677 RefPtr<MediaKeySession> session = 678 new MediaKeySession(GetParentObject(), this, mKeySystem, aSessionType, 679 isHardwareDecryption, aRv); 680 681 if (aRv.Failed()) { 682 return nullptr; 683 } 684 DDLINKCHILD("session", session.get()); 685 686 // Add session to the set of sessions awaiting their sessionId being ready. 687 EME_LOG("MediaKeys[%p]::CreateSession(aSessionType=%" PRIu8 688 ") putting session with token=%" PRIu32 " into mPendingSessions", 689 this, static_cast<uint8_t>(aSessionType), session->Token()); 690 mPendingSessions.InsertOrUpdate(session->Token(), RefPtr{session}); 691 692 return session.forget(); 693 } 694 695 void MediaKeys::OnSessionLoaded(PromiseId aId, bool aSuccess) { 696 EME_LOG("MediaKeys[%p]::OnSessionLoaded() resolve promise id=%" PRIu32, this, 697 aId); 698 699 ResolvePromiseWithResult(aId, aSuccess); 700 } 701 702 void MediaKeys::OnSessionClosed(MediaKeySession* aSession) { 703 nsAutoString id; 704 aSession->GetSessionId(id); 705 mKeySessions.Remove(id); 706 } 707 708 already_AddRefed<MediaKeySession> MediaKeys::GetSession( 709 const nsAString& aSessionId) { 710 RefPtr<MediaKeySession> session; 711 mKeySessions.Get(aSessionId, getter_AddRefs(session)); 712 return session.forget(); 713 } 714 715 already_AddRefed<MediaKeySession> MediaKeys::GetPendingSession( 716 uint32_t aToken) { 717 EME_LOG("MediaKeys[%p]::GetPendingSession(aToken=%" PRIu32 ")", this, aToken); 718 RefPtr<MediaKeySession> session; 719 mPendingSessions.Get(aToken, getter_AddRefs(session)); 720 mPendingSessions.Remove(aToken); 721 return session.forget(); 722 } 723 724 bool MediaKeys::IsBoundToMediaElement() const { 725 MOZ_ASSERT(NS_IsMainThread()); 726 return mElement != nullptr; 727 } 728 729 nsresult MediaKeys::Bind(HTMLMediaElement* aElement) { 730 MOZ_ASSERT(NS_IsMainThread()); 731 if (IsBoundToMediaElement()) { 732 return NS_ERROR_FAILURE; 733 } 734 735 mElement = aElement; 736 737 return NS_OK; 738 } 739 740 void MediaKeys::Unbind() { 741 MOZ_ASSERT(NS_IsMainThread()); 742 mElement = nullptr; 743 } 744 745 void MediaKeys::CheckIsElementCapturePossible() { 746 MOZ_ASSERT(NS_IsMainThread()); 747 EME_LOG("MediaKeys[%p]::IsElementCapturePossible()", this); 748 // Note, HTMLMediaElement prevents capture of its content via Capture APIs 749 // on the element if it has a media keys attached (see bug 1071482). So we 750 // don't need to check those cases here (they are covered by tests). 751 752 nsCOMPtr<nsIObserverService> observerService = 753 mozilla::services::GetObserverService(); 754 755 if (!observerService) { 756 // This can happen if we're in shutdown which means we may be going away 757 // soon anyway, but respond saying capture is possible since we can't 758 // forward the check further. 759 if (mProxy) { 760 mProxy->NotifyOutputProtectionStatus( 761 CDMProxy::OutputProtectionCheckStatus::CheckFailed, 762 CDMProxy::OutputProtectionCaptureStatus::Unused); 763 } 764 return; 765 } 766 if (!mObserverAdded) { 767 nsresult rv = 768 observerService->AddObserver(this, kMediaKeysResponseTopic, false); 769 if (NS_FAILED(rv)) { 770 if (mProxy) { 771 mProxy->NotifyOutputProtectionStatus( 772 CDMProxy::OutputProtectionCheckStatus::CheckFailed, 773 CDMProxy::OutputProtectionCaptureStatus::Unused); 774 } 775 return; 776 } 777 mObserverAdded = true; 778 } 779 780 if (mCaptureCheckRequestJson.IsEmpty()) { 781 // Lazily populate the JSON the first time we need it. 782 JSONStringWriteFunc<nsAutoCString> json; 783 JSONWriter jw{json}; 784 jw.Start(); 785 jw.StringProperty("status", "is-capture-possible"); 786 jw.StringProperty("keySystem", NS_ConvertUTF16toUTF8(mKeySystem)); 787 jw.End(); 788 mCaptureCheckRequestJson = NS_ConvertUTF8toUTF16(json.StringCRef()); 789 } 790 791 MOZ_DIAGNOSTIC_ASSERT(!mCaptureCheckRequestJson.IsEmpty()); 792 observerService->NotifyObservers(mParent.get(), kMediaKeysRequestTopic, 793 mCaptureCheckRequestJson.get()); 794 } 795 796 void MediaKeys::GetSessionsInfo(nsString& sessionsInfo) { 797 for (const auto& keySession : mKeySessions.Values()) { 798 nsString sessionID; 799 keySession->GetSessionId(sessionID); 800 sessionsInfo.AppendLiteral("(sid="); 801 sessionsInfo.Append(sessionID); 802 MediaKeyStatusMap* keyStatusMap = keySession->KeyStatuses(); 803 for (uint32_t i = 0; i < keyStatusMap->GetIterableLength(); i++) { 804 nsString keyID = keyStatusMap->GetKeyIDAsHexString(i); 805 sessionsInfo.AppendLiteral("(kid="); 806 sessionsInfo.Append(keyID); 807 sessionsInfo.AppendLiteral(" status="); 808 sessionsInfo.AppendASCII(GetEnumString(keyStatusMap->GetValueAtIndex(i))); 809 sessionsInfo.AppendLiteral(")"); 810 } 811 sessionsInfo.AppendLiteral(")"); 812 } 813 } 814 815 // https://w3c.github.io/encrypted-media/#dom-mediakeys-getstatusforpolicy 816 already_AddRefed<Promise> MediaKeys::GetStatusForPolicy( 817 const MediaKeysPolicy& aPolicy, ErrorResult& aRv) { 818 RefPtr<DetailedPromise> promise( 819 MakePromise(aRv, "MediaKeys::GetStatusForPolicy()"_ns)); 820 if (aRv.Failed()) { 821 return nullptr; 822 } 823 824 // 1. If policy has no present members, return a promise rejected with a newly 825 // created TypeError. 826 if (!aPolicy.mMinHdcpVersion.WasPassed()) { 827 promise->MaybeRejectWithTypeError("No minHdcpVersion in MediaKeysPolicy"); 828 return promise.forget(); 829 } 830 831 if (!mProxy) { 832 NS_WARNING("Tried to use a MediaKeys without a CDM"); 833 promise->MaybeRejectWithInvalidStateError( 834 "Null CDM in MediaKeys.GetStatusForPolicy()"); 835 return promise.forget(); 836 } 837 838 EME_LOG("GetStatusForPolicy minHdcpVersion = %s.", 839 GetEnumString(aPolicy.mMinHdcpVersion.Value()).get()); 840 mProxy->GetStatusForPolicy(StorePromise(promise), 841 aPolicy.mMinHdcpVersion.Value()); 842 return promise.forget(); 843 } 844 845 void MediaKeys::ResolvePromiseWithKeyStatus(PromiseId aId, 846 MediaKeyStatus aMediaKeyStatus) { 847 RefPtr<DetailedPromise> promise(RetrievePromise(aId)); 848 if (!promise) { 849 return; 850 } 851 RefPtr<MediaKeys> keys(this); 852 EME_LOG( 853 "MediaKeys[%p]::ResolvePromiseWithKeyStatus() resolve promise id=%" PRIu32 854 ", keystatus=%" PRIu8, 855 this, aId, static_cast<uint8_t>(aMediaKeyStatus)); 856 promise->MaybeResolve(aMediaKeyStatus); 857 } 858 859 nsCString MediaKeys::GetMediaKeySystemConfigurationString() const { 860 return MediaKeySystemAccess::ToCString(mConfig); 861 } 862 863 } // namespace mozilla::dom