MediaKeySystemAccessManager.cpp (29417B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "MediaKeySystemAccessManager.h" 6 7 #include "DecoderDoctorDiagnostics.h" 8 #include "MediaKeySystemAccessPermissionRequest.h" 9 #include "VideoUtils.h" 10 #include "mozilla/DetailedPromise.h" 11 #include "mozilla/EMEUtils.h" 12 #include "mozilla/Preferences.h" 13 #include "mozilla/Services.h" 14 #include "mozilla/StaticPrefs_media.h" 15 #include "mozilla/dom/BrowserChild.h" 16 #include "mozilla/dom/Document.h" 17 #include "mozilla/dom/KeySystemNames.h" 18 #include "nsComponentManagerUtils.h" 19 #include "nsContentUtils.h" 20 #include "nsIObserverService.h" 21 #include "nsIScriptError.h" 22 #include "nsPrintfCString.h" 23 #include "nsServiceManagerUtils.h" 24 #include "nsTHashMap.h" 25 26 namespace mozilla::dom { 27 28 MediaKeySystemAccessManager::PendingRequest::PendingRequest( 29 DetailedPromise* aPromise, const nsAString& aKeySystem, 30 const Sequence<MediaKeySystemConfiguration>& aConfigs) 31 : MediaKeySystemAccessRequest(aKeySystem, aConfigs), mPromise(aPromise) { 32 MOZ_COUNT_CTOR(MediaKeySystemAccessManager::PendingRequest); 33 } 34 35 MediaKeySystemAccessManager::PendingRequest::~PendingRequest() { 36 MOZ_COUNT_DTOR(MediaKeySystemAccessManager::PendingRequest); 37 } 38 39 void MediaKeySystemAccessManager::PendingRequest::CancelTimer() { 40 if (mTimer) { 41 mTimer->Cancel(); 42 mTimer = nullptr; 43 } 44 } 45 46 void MediaKeySystemAccessManager::PendingRequest:: 47 RejectPromiseWithInvalidAccessError(const nsACString& aReason) { 48 if (mPromise) { 49 mPromise->MaybeRejectWithInvalidAccessError(aReason); 50 } 51 } 52 53 void MediaKeySystemAccessManager::PendingRequest:: 54 RejectPromiseWithNotSupportedError(const nsACString& aReason) { 55 if (mPromise) { 56 mPromise->MaybeRejectWithNotSupportedError(aReason); 57 } 58 } 59 60 void MediaKeySystemAccessManager::PendingRequest::RejectPromiseWithTypeError( 61 const nsACString& aReason) { 62 if (mPromise) { 63 mPromise->MaybeRejectWithTypeError(aReason); 64 } 65 } 66 67 void MediaKeySystemAccessManager::PendingRequest::ResolvePromise( 68 MediaKeySystemAccess* aAccess) { 69 if (mPromise) { 70 mPromise->MaybeResolve(aAccess); 71 } 72 } 73 74 void MediaKeySystemAccessManager::PendingRequestWithMozPromise:: 75 RejectPromiseWithInvalidAccessError(const nsACString& aReason) { 76 mAccessPromise.RejectIfExists(NS_ERROR_FAILURE, __func__); 77 } 78 79 void MediaKeySystemAccessManager::PendingRequestWithMozPromise:: 80 RejectPromiseWithNotSupportedError(const nsACString& aReason) { 81 mAccessPromise.RejectIfExists(NS_ERROR_FAILURE, __func__); 82 } 83 84 void MediaKeySystemAccessManager::PendingRequestWithMozPromise:: 85 RejectPromiseWithTypeError(const nsACString& aReason) { 86 mAccessPromise.RejectIfExists(NS_ERROR_FAILURE, __func__); 87 } 88 89 void MediaKeySystemAccessManager::PendingRequestWithMozPromise::ResolvePromise( 90 MediaKeySystemAccess* aAccess) { 91 mAccessPromise.ResolveIfExists(aAccess, __func__); 92 } 93 94 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccessManager) 95 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver) 96 NS_INTERFACE_MAP_ENTRY(nsIObserver) 97 NS_INTERFACE_MAP_ENTRY(nsINamed) 98 NS_INTERFACE_MAP_END 99 100 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccessManager) 101 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccessManager) 102 103 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaKeySystemAccessManager) 104 105 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaKeySystemAccessManager) 106 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) 107 for (size_t i = 0; i < tmp->mPendingInstallRequests.Length(); i++) { 108 tmp->mPendingInstallRequests[i]->CancelTimer(); 109 tmp->mPendingInstallRequests[i]->RejectPromiseWithInvalidAccessError( 110 nsLiteralCString( 111 "Promise still outstanding at MediaKeySystemAccessManager GC")); 112 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingInstallRequests[i]->mPromise) 113 } 114 tmp->mPendingInstallRequests.Clear(); 115 for (size_t i = 0; i < tmp->mPendingAppApprovalRequests.Length(); i++) { 116 tmp->mPendingAppApprovalRequests[i]->RejectPromiseWithInvalidAccessError( 117 nsLiteralCString( 118 "Promise still outstanding at MediaKeySystemAccessManager GC")); 119 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAppApprovalRequests[i]->mPromise) 120 } 121 tmp->mPendingAppApprovalRequests.Clear(); 122 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 123 124 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaKeySystemAccessManager) 125 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) 126 for (size_t i = 0; i < tmp->mPendingInstallRequests.Length(); i++) { 127 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingInstallRequests[i]->mPromise) 128 } 129 for (size_t i = 0; i < tmp->mPendingAppApprovalRequests.Length(); i++) { 130 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAppApprovalRequests[i]->mPromise) 131 } 132 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 133 134 #define MKSAM_LOG_DEBUG(msg, ...) \ 135 EME_LOG("MediaKeySystemAccessManager::%s " msg, __func__, ##__VA_ARGS__) 136 137 MediaKeySystemAccessManager::MediaKeySystemAccessManager( 138 nsPIDOMWindowInner* aWindow) 139 : mWindow(aWindow) { 140 MOZ_ASSERT(NS_IsMainThread()); 141 } 142 143 MediaKeySystemAccessManager::~MediaKeySystemAccessManager() { 144 MOZ_ASSERT(NS_IsMainThread()); 145 Shutdown(); 146 } 147 148 void MediaKeySystemAccessManager::Request( 149 DetailedPromise* aPromise, const nsAString& aKeySystem, 150 const Sequence<MediaKeySystemConfiguration>& aConfigs) { 151 MOZ_ASSERT(NS_IsMainThread()); 152 CheckDoesWindowSupportProtectedMedia( 153 MakeUnique<PendingRequest>(aPromise, aKeySystem, aConfigs)); 154 } 155 156 RefPtr<MediaKeySystemAccessManager::MediaKeySystemAccessPromise> 157 MediaKeySystemAccessManager::Request( 158 const nsAString& aKeySystem, 159 const Sequence<MediaKeySystemConfiguration>& aConfigs) { 160 MOZ_ASSERT(NS_IsMainThread()); 161 auto request = MakeUnique<PendingRequestWithMozPromise>(aKeySystem, aConfigs); 162 RefPtr<MediaKeySystemAccessPromise> promise = 163 request->mAccessPromise.Ensure(__func__); 164 CheckDoesWindowSupportProtectedMedia(std::move(request)); 165 return promise; 166 } 167 168 void MediaKeySystemAccessManager::CheckDoesWindowSupportProtectedMedia( 169 UniquePtr<PendingRequest> aRequest) { 170 MOZ_ASSERT(NS_IsMainThread()); 171 MOZ_ASSERT(aRequest); 172 MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s", 173 NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); 174 175 // In Windows OS, some Firefox windows that host content cannot support 176 // protected content, so check the status of support for this window. 177 // On other platforms windows should always support protected media. 178 #ifdef XP_WIN 179 RefPtr<BrowserChild> browser(BrowserChild::GetFrom(mWindow)); 180 if (!browser) { 181 if (!XRE_IsParentProcess() || XRE_IsE10sParentProcess()) { 182 // In this case, there is no browser because the Navigator object has 183 // been disconnected from its window. Thus, reject the promise. 184 aRequest->RejectPromiseWithTypeError( 185 "Browsing context is no longer available"_ns); 186 } else { 187 // In this case, there is no browser because e10s is off. Proceed with 188 // the request with support since this scenario is always supported. 189 MKSAM_LOG_DEBUG("Allowing protected media on Windows with e10s off."); 190 191 OnDoesWindowSupportProtectedMedia(true, std::move(aRequest)); 192 } 193 194 return; 195 } 196 197 RefPtr<MediaKeySystemAccessManager> self(this); 198 199 MKSAM_LOG_DEBUG( 200 "Checking with browser if this window supports protected media."); 201 browser->DoesWindowSupportProtectedMedia()->Then( 202 GetCurrentSerialEventTarget(), __func__, 203 [self, request = std::move(aRequest)]( 204 const BrowserChild::IsWindowSupportingProtectedMediaPromise:: 205 ResolveOrRejectValue& value) mutable { 206 if (value.IsResolve()) { 207 self->OnDoesWindowSupportProtectedMedia(value.ResolveValue(), 208 std::move(request)); 209 } else { 210 EME_LOG( 211 "MediaKeySystemAccessManager::DoesWindowSupportProtectedMedia-" 212 "ResolveOrRejectLambda Failed to make IPC call to " 213 "IsWindowSupportingProtectedMedia: " 214 "reason=%d", 215 static_cast<int>(value.RejectValue())); 216 // Treat as failure. 217 self->OnDoesWindowSupportProtectedMedia(false, std::move(request)); 218 } 219 }); 220 221 #else 222 // Non-Windows OS windows always support protected media. 223 MKSAM_LOG_DEBUG( 224 "Allowing protected media because all non-Windows OS windows support " 225 "protected media."); 226 OnDoesWindowSupportProtectedMedia(true, std::move(aRequest)); 227 #endif 228 } 229 230 void MediaKeySystemAccessManager::OnDoesWindowSupportProtectedMedia( 231 bool aIsSupportedInWindow, UniquePtr<PendingRequest> aRequest) { 232 MOZ_ASSERT(NS_IsMainThread()); 233 MOZ_ASSERT(aRequest); 234 MKSAM_LOG_DEBUG("aIsSupportedInWindow=%s aRequest->mKeySystem=%s", 235 aIsSupportedInWindow ? "true" : "false", 236 NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); 237 238 if (!aIsSupportedInWindow) { 239 aRequest->RejectPromiseWithNotSupportedError( 240 "EME is not supported in this window"_ns); 241 return; 242 } 243 244 RequestMediaKeySystemAccess(std::move(aRequest)); 245 } 246 247 void MediaKeySystemAccessManager::CheckDoesAppAllowProtectedMedia( 248 UniquePtr<PendingRequest> aRequest) { 249 // At time of writing, only GeckoView is expected to leverage the need to 250 // approve EME requests from the application. However, this functionality 251 // can be tested on all platforms by manipulating the 252 // media.eme.require-app-approval + test prefs associated with 253 // MediaKeySystemPermissionRequest. 254 MOZ_ASSERT(NS_IsMainThread()); 255 MOZ_ASSERT(aRequest); 256 MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s", 257 NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); 258 259 if (!StaticPrefs::media_eme_require_app_approval()) { 260 MKSAM_LOG_DEBUG( 261 "media.eme.require-app-approval is false, allowing request."); 262 // We don't require app approval as the pref is not set. Treat as app 263 // approving by passing true to the handler. 264 OnDoesAppAllowProtectedMedia(true, std::move(aRequest)); 265 return; 266 } 267 268 if (mAppAllowsProtectedMediaPromiseRequest.Exists()) { 269 // We already have a pending permission request, we don't need to fire 270 // another. Just wait for the existing permission request to be handled 271 // and the result from that will be used to handle the current request. 272 MKSAM_LOG_DEBUG( 273 "mAppAllowsProtectedMediaPromiseRequest already exists. aRequest " 274 "addded to queue and will be handled when exising permission request " 275 "is serviced."); 276 mPendingAppApprovalRequests.AppendElement(std::move(aRequest)); 277 return; 278 } 279 280 RefPtr<MediaKeySystemAccessPermissionRequest> appApprovalRequest = 281 MediaKeySystemAccessPermissionRequest::Create(mWindow); 282 if (!appApprovalRequest) { 283 MKSAM_LOG_DEBUG( 284 "Failed to create app approval request! Blocking eme request as " 285 "fallback."); 286 aRequest->RejectPromiseWithInvalidAccessError(nsLiteralCString( 287 "Failed to create approval request to send to app embedding Gecko.")); 288 return; 289 } 290 291 // If we're not using testing prefs (which take precedence over cached 292 // values) and have a cached value, handle based on the cached value. 293 if (appApprovalRequest->CheckPromptPrefs() == 294 MediaKeySystemAccessPermissionRequest::PromptResult::Pending && 295 mAppAllowsProtectedMedia) { 296 MKSAM_LOG_DEBUG( 297 "Short circuiting based on mAppAllowsProtectedMedia cached value"); 298 OnDoesAppAllowProtectedMedia(*mAppAllowsProtectedMedia, 299 std::move(aRequest)); 300 return; 301 } 302 303 // Store the approval request, it will be handled when we get a response 304 // from the app. 305 mPendingAppApprovalRequests.AppendElement(std::move(aRequest)); 306 307 RefPtr<MediaKeySystemAccessPermissionRequest::RequestPromise> p = 308 appApprovalRequest->GetPromise(); 309 p->Then( 310 GetCurrentSerialEventTarget(), __func__, 311 // Allow callback 312 [this, 313 self = RefPtr<MediaKeySystemAccessManager>(this)](bool aRequestResult) { 314 MOZ_ASSERT(NS_IsMainThread()); 315 MOZ_ASSERT(aRequestResult, "Result should be true on allow callback!"); 316 mAppAllowsProtectedMediaPromiseRequest.Complete(); 317 // Cache result. 318 mAppAllowsProtectedMedia = Some(aRequestResult); 319 // For each pending request, handle it based on the app's response. 320 for (UniquePtr<PendingRequest>& approvalRequest : 321 mPendingAppApprovalRequests) { 322 OnDoesAppAllowProtectedMedia(*mAppAllowsProtectedMedia, 323 std::move(approvalRequest)); 324 } 325 self->mPendingAppApprovalRequests.Clear(); 326 }, 327 // Cancel callback 328 [this, 329 self = RefPtr<MediaKeySystemAccessManager>(this)](bool aRequestResult) { 330 MOZ_ASSERT(NS_IsMainThread()); 331 MOZ_ASSERT(!aRequestResult, 332 "Result should be false on cancel callback!"); 333 mAppAllowsProtectedMediaPromiseRequest.Complete(); 334 // Cache result. 335 mAppAllowsProtectedMedia = Some(aRequestResult); 336 // For each pending request, handle it based on the app's response. 337 for (UniquePtr<PendingRequest>& approvalRequest : 338 mPendingAppApprovalRequests) { 339 OnDoesAppAllowProtectedMedia(*mAppAllowsProtectedMedia, 340 std::move(approvalRequest)); 341 } 342 self->mPendingAppApprovalRequests.Clear(); 343 }) 344 ->Track(mAppAllowsProtectedMediaPromiseRequest); 345 346 // Prefs not causing short circuit, no cached value, go ahead and request 347 // permission. 348 MKSAM_LOG_DEBUG("Dispatching async request for app approval"); 349 if (NS_FAILED(appApprovalRequest->Start())) { 350 // This shouldn't happen unless we're shutting down or similar edge cases. 351 // If this is regularly being hit then something is wrong and should be 352 // investigated. 353 MKSAM_LOG_DEBUG( 354 "Failed to start app approval request! Eme approval will be left in " 355 "limbo!"); 356 } 357 } 358 359 void MediaKeySystemAccessManager::OnDoesAppAllowProtectedMedia( 360 bool aIsAllowed, UniquePtr<PendingRequest> aRequest) { 361 MOZ_ASSERT(NS_IsMainThread()); 362 MOZ_ASSERT(aRequest); 363 MKSAM_LOG_DEBUG("aIsAllowed=%s aRequest->mKeySystem=%s", 364 aIsAllowed ? "true" : "false", 365 NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); 366 if (!aIsAllowed) { 367 aRequest->RejectPromiseWithNotSupportedError( 368 nsLiteralCString("The application embedding this user agent has " 369 "blocked MediaKeySystemAccess")); 370 return; 371 } 372 373 ProvideAccess(std::move(aRequest)); 374 } 375 376 void MediaKeySystemAccessManager::RequestMediaKeySystemAccess( 377 UniquePtr<PendingRequest> aRequest) { 378 MOZ_ASSERT(NS_IsMainThread()); 379 MOZ_ASSERT(aRequest); 380 MKSAM_LOG_DEBUG("aIsSupportedInWindow=%s", 381 NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); 382 383 // 1. If keySystem is the empty string, return a promise rejected with a newly 384 // created TypeError. 385 if (aRequest->mKeySystem.IsEmpty()) { 386 aRequest->RejectPromiseWithTypeError("Key system string is empty"_ns); 387 // Don't notify DecoderDoctor, as there's nothing we or the user can 388 // do to fix this situation; the site is using the API wrong. 389 return; 390 } 391 // 2. If supportedConfigurations is empty, return a promise rejected with a 392 // newly created TypeError. 393 if (aRequest->mConfigs.IsEmpty()) { 394 aRequest->RejectPromiseWithTypeError( 395 "Candidate MediaKeySystemConfigs is empty"_ns); 396 // Don't notify DecoderDoctor, as there's nothing we or the user can 397 // do to fix this situation; the site is using the API wrong. 398 return; 399 } 400 401 // 3. Let document be the calling context's Document. 402 // 4. Let origin be the origin of document. 403 // 5. Let promise be a new promise. 404 // 6. Run the following steps in parallel: 405 406 // 1. If keySystem is not one of the Key Systems supported by the user 407 // agent, reject promise with a NotSupportedError. String comparison is 408 // case-sensitive. 409 if (!IsWidevineKeySystem(aRequest->mKeySystem) && 410 #ifdef MOZ_WMF_CDM 411 !IsPlayReadyKeySystemAndSupported(aRequest->mKeySystem) && 412 !IsWidevineExperimentKeySystemAndSupported(aRequest->mKeySystem) && 413 #endif 414 !IsClearkeyKeySystem(aRequest->mKeySystem)) { 415 // Not to inform user, because nothing to do if the keySystem is not 416 // supported. 417 aRequest->RejectPromiseWithNotSupportedError( 418 "Key system is unsupported"_ns); 419 aRequest->mDiagnostics.StoreMediaKeySystemAccess( 420 mWindow->GetExtantDoc(), aRequest->mKeySystem, false, __func__); 421 return; 422 } 423 424 if (!StaticPrefs::media_eme_enabled() && 425 !IsClearkeyKeySystem(aRequest->mKeySystem)) { 426 // EME disabled by user, send notification to chrome so UI can inform user. 427 // Clearkey is allowed even when EME is disabled because we want the pref 428 // "media.eme.enabled" only taking effect on proprietary DRMs. 429 // We don't show the notification if the pref is locked. 430 if (!Preferences::IsLocked("media.eme.enabled")) { 431 MediaKeySystemAccess::NotifyObservers(mWindow, aRequest->mKeySystem, 432 MediaKeySystemStatus::Api_disabled); 433 } 434 aRequest->RejectPromiseWithNotSupportedError("EME has been preffed off"_ns); 435 aRequest->mDiagnostics.StoreMediaKeySystemAccess( 436 mWindow->GetExtantDoc(), aRequest->mKeySystem, false, __func__); 437 return; 438 } 439 440 nsAutoCString message; 441 MediaKeySystemStatus status = 442 MediaKeySystemAccess::GetKeySystemStatus(*aRequest, message); 443 444 nsPrintfCString msg( 445 "MediaKeySystemAccess::GetKeySystemStatus(%s) " 446 "result=%s msg='%s'", 447 NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get(), 448 GetEnumString(status).get(), message.get()); 449 LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg)); 450 EME_LOG("%s", msg.get()); 451 452 // We may need to install Widevine CDM to continue. 453 if (status == MediaKeySystemStatus::Cdm_not_installed && 454 (IsWidevineKeySystem(aRequest->mKeySystem) 455 #ifdef MOZ_WMF_CDM 456 || IsWidevineExperimentKeySystemAndSupported(aRequest->mKeySystem) 457 #endif 458 )) { 459 // These are cases which could be resolved by downloading a new(er) CDM. 460 // When we send the status to chrome, chrome's GMPProvider will attempt to 461 // download or update the CDM. In AwaitInstall() we add listeners to wait 462 // for the update to complete, and we'll call this function again with 463 // aType==Subsequent once the download has completed and the GMPService 464 // has had a new plugin added. AwaitInstall() sets a timer to fail if the 465 // update/download takes too long or fails. 466 467 if (aRequest->mRequestType != PendingRequest::RequestType::Initial) { 468 MOZ_ASSERT(aRequest->mRequestType == 469 PendingRequest::RequestType::Subsequent); 470 // CDM is not installed, but this is a subsequent request. We've waited, 471 // but can't service this request! Give up. Chrome will still be showing a 472 // "I can't play, updating" notification. 473 aRequest->RejectPromiseWithNotSupportedError( 474 "Timed out while waiting for a CDM update"_ns); 475 aRequest->mDiagnostics.StoreMediaKeySystemAccess( 476 mWindow->GetExtantDoc(), aRequest->mKeySystem, false, __func__); 477 return; 478 } 479 480 nsString keySystem = aRequest->mKeySystem; 481 #ifdef MOZ_WMF_CDM 482 // If cdm-not-install is for HWDRM, that means we want to install Widevine 483 // L1, which requires using hardware key system name for GMP to look up the 484 // plugin. 485 if (CheckIfHarewareDRMConfigExists(aRequest->mConfigs)) { 486 keySystem = NS_ConvertUTF8toUTF16(kWidevineExperimentKeySystemName); 487 } 488 #endif 489 auto& diagnostics = aRequest->mDiagnostics; 490 if (AwaitInstall(std::move(aRequest))) { 491 // Notify chrome that we're going to wait for the CDM to download/update. 492 EME_LOG("Await %s for installation", 493 NS_ConvertUTF16toUTF8(keySystem).get()); 494 MediaKeySystemAccess::NotifyObservers(mWindow, keySystem, status); 495 } else { 496 // Failed to await the install. Log failure and give up trying to service 497 // this request. 498 EME_LOG("Failed to await %s for installation", 499 NS_ConvertUTF16toUTF8(keySystem).get()); 500 diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(), keySystem, 501 false, __func__); 502 } 503 return; 504 } 505 if (status != MediaKeySystemStatus::Available) { 506 // Failed due to user disabling something, send a notification to 507 // chrome, so we can show some UI to explain how the user can rectify 508 // the situation. 509 EME_LOG("Notify CDM failure for %s and reject the promise", 510 NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); 511 MediaKeySystemAccess::NotifyObservers(mWindow, aRequest->mKeySystem, 512 status); 513 aRequest->RejectPromiseWithNotSupportedError(message); 514 return; 515 } 516 517 bool isPrivateBrowsing = 518 mWindow->GetExtantDoc() && 519 mWindow->GetExtantDoc()->NodePrincipal()->GetIsInPrivateBrowsing(); 520 // 2. Let implementation be the implementation of keySystem. 521 // 3. For each value in supportedConfigurations: 522 // 1. Let candidate configuration be the value. 523 // 2. Let supported configuration be the result of executing the Get 524 // Supported Configuration algorithm on implementation, candidate 525 // configuration, and origin. 526 // 3. If supported configuration is not NotSupported, run the following 527 // steps: 528 // 1. Let access be a new MediaKeySystemAccess object, and initialize it 529 // as follows: 530 // 1. Set the keySystem attribute to keySystem. 531 // 2. Let the configuration value be supported configuration. 532 // 3. Let the cdm implementation value be implementation. 533 // 2. Resolve promise with access and abort the parallel steps of this 534 // algorithm. 535 MediaKeySystemAccess::GetSupportedConfig(aRequest.get(), isPrivateBrowsing, 536 mWindow->GetExtantDoc()) 537 ->Then(GetMainThreadSerialEventTarget(), __func__, 538 [self = RefPtr<MediaKeySystemAccessManager>{this}, this, 539 request = UniquePtr<PendingRequest>{std::move(aRequest)}]( 540 const KeySystemConfig::KeySystemConfigPromise:: 541 ResolveOrRejectValue& aResult) mutable { 542 if (aResult.IsResolve()) { 543 request->mSupportedConfig = Some(aResult.ResolveValue()); 544 // The app gets the final say on if we provide access or not. 545 CheckDoesAppAllowProtectedMedia(std::move(request)); 546 } else { 547 // 4. Reject promise with a NotSupportedError. 548 // Not to inform user, because nothing to do if the 549 // corresponding keySystem configuration is not supported. 550 request->RejectPromiseWithNotSupportedError( 551 "Key system configuration is not supported"_ns); 552 request->mDiagnostics.StoreMediaKeySystemAccess( 553 mWindow->GetExtantDoc(), request->mKeySystem, false, 554 __func__); 555 } 556 }); 557 } 558 559 void MediaKeySystemAccessManager::ProvideAccess( 560 UniquePtr<PendingRequest> aRequest) { 561 MOZ_ASSERT(aRequest); 562 MOZ_ASSERT( 563 aRequest->mSupportedConfig, 564 "The request needs a supported config if we're going to provide access!"); 565 MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s", 566 NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); 567 568 DecoderDoctorDiagnostics diagnostics; 569 570 RefPtr<MediaKeySystemAccess> access(new MediaKeySystemAccess( 571 mWindow, aRequest->mKeySystem, aRequest->mSupportedConfig.ref())); 572 aRequest->ResolvePromise(access); 573 diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(), 574 aRequest->mKeySystem, true, __func__); 575 } 576 577 bool MediaKeySystemAccessManager::AwaitInstall( 578 UniquePtr<PendingRequest> aRequest) { 579 MOZ_ASSERT(NS_IsMainThread()); 580 MOZ_ASSERT(aRequest); 581 MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s", 582 NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); 583 584 if (!EnsureObserversAdded()) { 585 NS_WARNING("Failed to add pref observer"); 586 aRequest->RejectPromiseWithNotSupportedError(nsLiteralCString( 587 "Failed trying to setup CDM update: failed adding observers")); 588 return false; 589 } 590 591 nsCOMPtr<nsITimer> timer; 592 NS_NewTimerWithObserver(getter_AddRefs(timer), this, 60 * 1000, 593 nsITimer::TYPE_ONE_SHOT); 594 if (!timer) { 595 NS_WARNING("Failed to create timer to await CDM install."); 596 aRequest->RejectPromiseWithNotSupportedError(nsLiteralCString( 597 "Failed trying to setup CDM update: failed timer creation")); 598 return false; 599 } 600 601 MOZ_DIAGNOSTIC_ASSERT( 602 aRequest->mTimer == nullptr, 603 "Timer should not already be set on a request we're about to await"); 604 aRequest->mTimer = timer; 605 606 mPendingInstallRequests.AppendElement(std::move(aRequest)); 607 return true; 608 } 609 610 void MediaKeySystemAccessManager::RetryRequest( 611 UniquePtr<PendingRequest> aRequest) { 612 MOZ_ASSERT(NS_IsMainThread()); 613 MOZ_ASSERT(aRequest); 614 MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s", 615 NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); 616 // Cancel and null timer if it exists. 617 aRequest->CancelTimer(); 618 // Indicate that this is a request that's being retried. 619 aRequest->mRequestType = PendingRequest::RequestType::Subsequent; 620 RequestMediaKeySystemAccess(std::move(aRequest)); 621 } 622 623 nsresult MediaKeySystemAccessManager::Observe(nsISupports* aSubject, 624 const char* aTopic, 625 const char16_t* aData) { 626 MOZ_ASSERT(NS_IsMainThread()); 627 MKSAM_LOG_DEBUG("%s", aTopic); 628 629 if (!strcmp(aTopic, "gmp-changed")) { 630 // Filter out the requests where the CDM's install-status is no longer 631 // "unavailable". This will be the CDMs which have downloaded since the 632 // initial request. 633 // Note: We don't have a way to communicate from chrome that the CDM has 634 // failed to download, so we'll just let the timeout fail us in that case. 635 nsTArray<UniquePtr<PendingRequest>> requests; 636 for (size_t i = mPendingInstallRequests.Length(); i-- > 0;) { 637 nsAutoCString message; 638 MediaKeySystemStatus status = MediaKeySystemAccess::GetKeySystemStatus( 639 *mPendingInstallRequests[i], message); 640 if (status == MediaKeySystemStatus::Cdm_not_installed) { 641 // Not yet installed, don't retry. Keep waiting until timeout. 642 continue; 643 } 644 // Status has changed, retry request. 645 requests.AppendElement(std::move(mPendingInstallRequests[i])); 646 mPendingInstallRequests.RemoveElementAt(i); 647 } 648 // Retry all pending requests, but this time fail if the CDM is not 649 // installed. 650 for (size_t i = requests.Length(); i-- > 0;) { 651 RetryRequest(std::move(requests[i])); 652 } 653 } else if (!strcmp(aTopic, "timer-callback")) { 654 // Find the timer that expired and re-run the request for it. 655 nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject)); 656 for (size_t i = 0; i < mPendingInstallRequests.Length(); i++) { 657 if (mPendingInstallRequests[i]->mTimer == timer) { 658 EME_LOG("MediaKeySystemAccessManager::AwaitInstall resuming request"); 659 UniquePtr<PendingRequest> request = 660 std::move(mPendingInstallRequests[i]); 661 mPendingInstallRequests.RemoveElementAt(i); 662 RetryRequest(std::move(request)); 663 break; 664 } 665 } 666 } 667 return NS_OK; 668 } 669 670 nsresult MediaKeySystemAccessManager::GetName(nsACString& aName) { 671 aName.AssignLiteral("MediaKeySystemAccessManager"); 672 return NS_OK; 673 } 674 675 bool MediaKeySystemAccessManager::EnsureObserversAdded() { 676 MOZ_ASSERT(NS_IsMainThread()); 677 if (mAddedObservers) { 678 return true; 679 } 680 681 nsCOMPtr<nsIObserverService> obsService = 682 mozilla::services::GetObserverService(); 683 if (NS_WARN_IF(!obsService)) { 684 return false; 685 } 686 mAddedObservers = 687 NS_SUCCEEDED(obsService->AddObserver(this, "gmp-changed", false)); 688 return mAddedObservers; 689 } 690 691 void MediaKeySystemAccessManager::Shutdown() { 692 MOZ_ASSERT(NS_IsMainThread()); 693 MKSAM_LOG_DEBUG(""); 694 for (const UniquePtr<PendingRequest>& installRequest : 695 mPendingInstallRequests) { 696 // Cancel all requests; we're shutting down. 697 installRequest->CancelTimer(); 698 installRequest->RejectPromiseWithInvalidAccessError(nsLiteralCString( 699 "Promise still outstanding at MediaKeySystemAccessManager shutdown")); 700 } 701 mPendingInstallRequests.Clear(); 702 for (const UniquePtr<PendingRequest>& approvalRequest : 703 mPendingAppApprovalRequests) { 704 approvalRequest->RejectPromiseWithInvalidAccessError(nsLiteralCString( 705 "Promise still outstanding at MediaKeySystemAccessManager shutdown")); 706 } 707 mPendingAppApprovalRequests.Clear(); 708 mAppAllowsProtectedMediaPromiseRequest.DisconnectIfExists(); 709 if (mAddedObservers) { 710 nsCOMPtr<nsIObserverService> obsService = 711 mozilla::services::GetObserverService(); 712 if (obsService) { 713 obsService->RemoveObserver(this, "gmp-changed"); 714 mAddedObservers = false; 715 } 716 } 717 } 718 719 } // namespace mozilla::dom 720 721 #undef MKSAM_LOG_DEBUG