MediaKeySystemAccess.cpp (52427B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 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 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/MediaKeySystemAccess.h" 8 9 #include <functional> 10 11 #include "DecoderDoctorDiagnostics.h" 12 #include "DecoderTraits.h" 13 #include "MP4Decoder.h" 14 #include "MediaContainerType.h" 15 #include "WebMDecoder.h" 16 #include "mozilla/ClearOnShutdown.h" 17 #include "mozilla/EMEUtils.h" 18 #include "mozilla/Preferences.h" 19 #include "mozilla/Services.h" 20 #include "mozilla/StaticPrefs_media.h" 21 #include "mozilla/dom/Document.h" 22 #include "mozilla/dom/KeySystemNames.h" 23 #include "mozilla/dom/MediaKeySession.h" 24 #include "mozilla/dom/MediaKeySystemAccessBinding.h" 25 #include "mozilla/dom/MediaKeySystemAccessManager.h" 26 #include "mozilla/dom/MediaSource.h" 27 #include "mozilla/dom/PContent.h" 28 #include "nsDOMString.h" 29 #include "nsIObserverService.h" 30 #include "nsMimeTypes.h" 31 #include "nsReadableUtils.h" 32 #include "nsServiceManagerUtils.h" 33 #include "nsUnicharUtils.h" 34 35 #ifdef XP_WIN 36 # include "PDMFactory.h" 37 # include "WMFDecoderModule.h" 38 #endif 39 #ifdef MOZ_WIDGET_ANDROID 40 # include "mozilla/java/MediaDrmProxyWrappers.h" 41 #endif 42 43 namespace mozilla::dom { 44 45 #ifdef MOZ_WMF_CDM 46 # include "nsIWindowsMediaFoundationCDMOriginsListService.h" 47 48 constinit static nsTArray<IPCOriginStatusEntry> sOriginStatusEntries; 49 #endif 50 51 #define LOG(msg, ...) \ 52 EME_LOG("MediaKeySystemAccess::%s " msg, __func__, ##__VA_ARGS__) 53 54 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess, mParent) 55 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess) 56 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess) 57 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccess) 58 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 59 NS_INTERFACE_MAP_ENTRY(nsISupports) 60 NS_INTERFACE_MAP_END 61 62 MediaKeySystemAccess::MediaKeySystemAccess( 63 nsPIDOMWindowInner* aParent, const nsAString& aKeySystem, 64 const MediaKeySystemConfiguration& aConfig) 65 : mParent(aParent), mKeySystem(aKeySystem), mConfig(aConfig) { 66 LOG("Created MediaKeySystemAccess for keysystem=%s config=%s", 67 NS_ConvertUTF16toUTF8(mKeySystem).get(), ToCString(mConfig).get()); 68 } 69 70 MediaKeySystemAccess::~MediaKeySystemAccess() = default; 71 72 JSObject* MediaKeySystemAccess::WrapObject(JSContext* aCx, 73 JS::Handle<JSObject*> aGivenProto) { 74 return MediaKeySystemAccess_Binding::Wrap(aCx, this, aGivenProto); 75 } 76 77 nsPIDOMWindowInner* MediaKeySystemAccess::GetParentObject() const { 78 return mParent; 79 } 80 81 void MediaKeySystemAccess::GetKeySystem(nsString& aOutKeySystem) const { 82 aOutKeySystem.Assign(mKeySystem); 83 } 84 85 void MediaKeySystemAccess::GetConfiguration( 86 MediaKeySystemConfiguration& aConfig) { 87 aConfig = mConfig; 88 } 89 90 already_AddRefed<Promise> MediaKeySystemAccess::CreateMediaKeys( 91 ErrorResult& aRv) { 92 RefPtr<MediaKeys> keys(new MediaKeys(mParent, mKeySystem, mConfig)); 93 return keys->Init(aRv); 94 } 95 96 enum class SecureLevel { 97 Software, 98 Hardware, 99 }; 100 101 static MediaKeySystemStatus EnsureCDMInstalled(const nsAString& aKeySystem, 102 const SecureLevel aSecure, 103 nsACString& aOutMessage) { 104 if (aSecure == SecureLevel::Software && 105 !KeySystemConfig::Supports(aKeySystem)) { 106 aOutMessage = "CDM is not installed"_ns; 107 return MediaKeySystemStatus::Cdm_not_installed; 108 } 109 110 #ifdef MOZ_WMF_CDM 111 if (aSecure == SecureLevel::Hardware) { 112 // Ensure we check the hardware key system name. 113 nsAutoString hardwareKeySystem; 114 if (IsWidevineKeySystem(aKeySystem) || 115 IsWidevineExperimentKeySystemAndSupported(aKeySystem)) { 116 hardwareKeySystem = 117 NS_ConvertUTF8toUTF16(kWidevineExperimentKeySystemName); 118 } else if (IsPlayReadyKeySystemAndSupported(aKeySystem)) { 119 hardwareKeySystem = NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware); 120 } else { 121 MOZ_ASSERT_UNREACHABLE("Not supported key system for HWDRM!"); 122 } 123 if (!KeySystemConfig::Supports(hardwareKeySystem)) { 124 aOutMessage = "CDM is not installed"_ns; 125 return MediaKeySystemStatus::Cdm_not_installed; 126 } 127 } 128 #endif 129 130 return MediaKeySystemStatus::Available; 131 } 132 133 /* static */ 134 MediaKeySystemStatus MediaKeySystemAccess::GetKeySystemStatus( 135 const MediaKeySystemAccessRequest& aRequest, nsACString& aOutMessage) { 136 const nsString& keySystem = aRequest.mKeySystem; 137 138 MOZ_ASSERT(StaticPrefs::media_eme_enabled() || 139 IsClearkeyKeySystem(keySystem)); 140 141 LOG("checking if CDM is installed or disabled for %s", 142 NS_ConvertUTF16toUTF8(keySystem).get()); 143 if (IsClearkeyKeySystem(keySystem)) { 144 return EnsureCDMInstalled(keySystem, SecureLevel::Software, aOutMessage); 145 } 146 147 // This is used to determine if we need to download Widevine L1. 148 bool shouldCheckL1Installation = false; 149 #ifdef MOZ_WMF_CDM 150 if (StaticPrefs::media_eme_widevine_experiment_enabled()) { 151 shouldCheckL1Installation = 152 CheckIfHarewareDRMConfigExists(aRequest.mConfigs) || 153 IsWidevineExperimentKeySystemAndSupported(keySystem); 154 } 155 #endif 156 157 // Check Widevine L3 158 if (IsWidevineKeySystem(keySystem) && !shouldCheckL1Installation) { 159 if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) { 160 if (!Preferences::GetBool("media.gmp-widevinecdm.enabled", false)) { 161 aOutMessage = "Widevine EME disabled"_ns; 162 return MediaKeySystemStatus::Cdm_disabled; 163 } 164 return EnsureCDMInstalled(keySystem, SecureLevel::Software, aOutMessage); 165 #ifdef MOZ_WIDGET_ANDROID 166 } else if (Preferences::GetBool("media.mediadrm-widevinecdm.visible", 167 false)) { 168 bool supported = 169 mozilla::java::MediaDrmProxy::IsSchemeSupported(keySystem); 170 if (!supported) { 171 aOutMessage = nsLiteralCString( 172 "KeySystem or Minimum API level not met for Widevine EME"); 173 return MediaKeySystemStatus::Cdm_not_supported; 174 } 175 return MediaKeySystemStatus::Available; 176 #endif 177 } 178 } 179 180 #ifdef MOZ_WMF_CDM 181 // Check PlayReady, which is a built-in CDM on most Windows versions. 182 if (IsPlayReadyKeySystemAndSupported(keySystem) && 183 KeySystemConfig::Supports(keySystem)) { 184 return MediaKeySystemStatus::Available; 185 } 186 187 // Check Widevine L1. This can be a request for experimental key systems, or 188 // for a normal key system with hardware robustness. 189 if ((IsWidevineExperimentKeySystemAndSupported(keySystem) || 190 IsWidevineKeySystem(keySystem)) && 191 shouldCheckL1Installation) { 192 // TODO : if L3 hasn't been installed as well, should we fallback to install 193 // L3? 194 if (!Preferences::GetBool("media.gmp-widevinecdm-l1.enabled", false)) { 195 aOutMessage = "Widevine L1 EME disabled"_ns; 196 return MediaKeySystemStatus::Cdm_disabled; 197 } 198 return EnsureCDMInstalled(keySystem, SecureLevel::Hardware, aOutMessage); 199 } 200 #endif 201 202 return MediaKeySystemStatus::Cdm_not_supported; 203 } 204 205 static KeySystemConfig::EMECodecString ToEMEAPICodecString( 206 const nsString& aCodec) { 207 if (IsAACCodecString(aCodec)) { 208 return KeySystemConfig::EME_CODEC_AAC; 209 } 210 if (aCodec.EqualsLiteral("opus")) { 211 return KeySystemConfig::EME_CODEC_OPUS; 212 } 213 if (aCodec.EqualsLiteral("vorbis")) { 214 return KeySystemConfig::EME_CODEC_VORBIS; 215 } 216 if (aCodec.EqualsLiteral("flac")) { 217 return KeySystemConfig::EME_CODEC_FLAC; 218 } 219 if (IsH264CodecString(aCodec)) { 220 return KeySystemConfig::EME_CODEC_H264; 221 } 222 if (IsAV1CodecString(aCodec)) { 223 return KeySystemConfig::EME_CODEC_AV1; 224 } 225 if (IsVP8CodecString(aCodec)) { 226 return KeySystemConfig::EME_CODEC_VP8; 227 } 228 if (IsVP9CodecString(aCodec)) { 229 return KeySystemConfig::EME_CODEC_VP9; 230 } 231 #ifdef MOZ_WMF 232 if (IsH265CodecString(aCodec)) { 233 return KeySystemConfig::EME_CODEC_HEVC; 234 } 235 #endif 236 return ""_ns; 237 } 238 239 #ifdef MOZ_WMF_CDM 240 /* static */ 241 void MediaKeySystemAccess::UpdateMFCDMOriginEntries( 242 const nsTArray<IPCOriginStatusEntry>& aEntries) { 243 MOZ_ASSERT(NS_IsMainThread()); 244 static bool sXPCOMShutdown = false; 245 if (sXPCOMShutdown) { 246 EME_LOG("XPCOM shutdown detected; entry update aborted"); 247 return; 248 } 249 sOriginStatusEntries.Clear(); 250 sOriginStatusEntries.AppendElements(aEntries); 251 EME_LOG("UpdateMFCDMOriginEntries"); 252 for (const auto& entry : sOriginStatusEntries) { 253 EME_LOG("-- Origin: %s, Status: %d\n", entry.origin().get(), 254 entry.status()); 255 } 256 RunOnShutdown([&] { 257 sOriginStatusEntries.Clear(); 258 sXPCOMShutdown = true; 259 }); 260 } 261 262 static bool IsMFCDMAllowedByOrigin(const Maybe<nsCString>& aOrigin) { 263 // TODO: Remove hardcoded allowed and blocked lists once the Remote Settings 264 // lists are verified to be reliable in bug 1964811. 265 // 0 : disabled, 1 : enabled allowed list, 2 : enabled blocked list, 266 // 3 : enabled list status control via Remote Setting 267 enum Filer : uint32_t { 268 eDisable = 0, 269 eAllowedListEnabled = 1, 270 eBlockedListEnabled = 2, 271 eAllowedByDefaultRemoteSettings = 3, 272 eBlockedByDefaultRemoteSettings = 4, 273 }; 274 const auto prefValue = StaticPrefs::media_eme_mfcdm_origin_filter_enabled(); 275 if (prefValue == Filer::eDisable || !aOrigin || 276 !IsMediaFoundationCDMPlaybackEnabled()) { 277 // No need to check the origin. 278 return true; 279 } 280 281 // Check if the origin is allowed to use MFCDM. 282 if (prefValue == Filer::eAllowedListEnabled) { 283 static nsTArray<nsCString> kAllowedOrigins({ 284 "https://www.netflix.com"_ns, 285 }); 286 for (const auto& allowedOrigin : kAllowedOrigins) { 287 if (FindInReadable(allowedOrigin, *aOrigin)) { 288 EME_LOG( 289 "MediaKeySystemAccess::IsMFCDMAllowedByOrigin, origin " 290 "(%s) is ALLOWED to use MFCDM", 291 aOrigin->get()); 292 return true; 293 } 294 } 295 EME_LOG( 296 "MediaKeySystemAccess::IsMFCDMAllowedByOrigin, origin (%s) is " 297 "not allowed to use MFCDM", 298 aOrigin->get()); 299 return false; 300 } 301 302 // Check if the origin is blocked to use MFCDM. 303 if (prefValue == Filer::eBlockedListEnabled) { 304 static nsTArray<nsCString> kBlockedOrigins({ 305 "https://on.orf.at"_ns, 306 "https://www.hulu.com"_ns, 307 }); 308 for (const auto& blockedOrigin : kBlockedOrigins) { 309 if (FindInReadable(blockedOrigin, *aOrigin)) { 310 EME_LOG( 311 "MediaKeySystemAccess::IsMFCDMAllowedByOrigin, origin (%s) " 312 "is BLOCKED to use MFCDM", 313 aOrigin->get()); 314 return false; 315 } 316 } 317 EME_LOG( 318 "MediaKeySystemAccess::IsMFCDMAllowedByOrigin, origin (%s) " 319 "is allowed to use MFCDM", 320 aOrigin->get()); 321 return true; 322 } 323 324 // List from Remote Settings. No duplicated origins; suborigins follow the 325 // main origin's result. 326 bool isAllowed = prefValue == Filer::eAllowedByDefaultRemoteSettings; 327 bool isFound = false; 328 for (const auto& entry : sOriginStatusEntries) { 329 // Check if the given origin matches the entry, or if it's a suborigin. 330 if (FindInReadable(entry.origin(), *aOrigin)) { 331 isAllowed = 332 entry.status() == 333 nsIWindowsMediaFoundationCDMOriginsListService::ORIGIN_ALLOWED; 334 isFound = true; 335 break; 336 } 337 } 338 EME_LOG( 339 "MediaKeySystemAccess::IsMFCDMAllowedByOrigin, origin (%s) " 340 "is %s to use MFCDM %s(Remote)", 341 aOrigin->get(), isAllowed ? "ALLOWED" : "BLOCKED", 342 isFound ? "" : "by default "); 343 return isAllowed; 344 } 345 #endif 346 347 static RefPtr<KeySystemConfig::SupportedConfigsPromise> 348 GetSupportedKeySystemConfigs(const nsAString& aKeySystem, 349 bool aIsHardwareDecryption, 350 bool aIsPrivateBrowsing, 351 const Maybe<nsCString>& aOrigin) { 352 using DecryptionInfo = KeySystemConfig::DecryptionInfo; 353 nsTArray<KeySystemConfigRequest> requests; 354 355 // Software Widevine and Clearkey 356 if (IsWidevineKeySystem(aKeySystem) || IsClearkeyKeySystem(aKeySystem)) { 357 requests.AppendElement(KeySystemConfigRequest{ 358 aKeySystem, DecryptionInfo::Software, aIsPrivateBrowsing}); 359 } 360 #ifdef MOZ_WMF_CDM 361 if (IsMFCDMAllowedByOrigin(aOrigin)) { 362 if (IsPlayReadyEnabled()) { 363 // PlayReady software and hardware 364 if (aKeySystem.EqualsLiteral(kPlayReadyKeySystemName) || 365 aKeySystem.EqualsLiteral(kPlayReadyKeySystemHardware)) { 366 requests.AppendElement(KeySystemConfigRequest{ 367 NS_ConvertUTF8toUTF16(kPlayReadyKeySystemName), 368 DecryptionInfo::Software, aIsPrivateBrowsing}); 369 if (aIsHardwareDecryption) { 370 requests.AppendElement(KeySystemConfigRequest{ 371 NS_ConvertUTF8toUTF16(kPlayReadyKeySystemName), 372 DecryptionInfo::Hardware, aIsPrivateBrowsing}); 373 requests.AppendElement(KeySystemConfigRequest{ 374 NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware), 375 DecryptionInfo::Hardware, aIsPrivateBrowsing}); 376 } 377 } 378 // PlayReady clearlead 379 if (aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName)) { 380 requests.AppendElement(KeySystemConfigRequest{ 381 NS_ConvertUTF8toUTF16(kPlayReadyHardwareClearLeadKeySystemName), 382 DecryptionInfo::Hardware, aIsPrivateBrowsing}); 383 } 384 } 385 386 if (IsWidevineHardwareDecryptionEnabled()) { 387 // Widevine hardware 388 if (aKeySystem.EqualsLiteral(kWidevineExperimentKeySystemName) || 389 (IsWidevineKeySystem(aKeySystem) && aIsHardwareDecryption)) { 390 requests.AppendElement(KeySystemConfigRequest{ 391 NS_ConvertUTF8toUTF16(kWidevineExperimentKeySystemName), 392 DecryptionInfo::Hardware, aIsPrivateBrowsing}); 393 } 394 // Widevine clearlead 395 if (aKeySystem.EqualsLiteral(kWidevineExperiment2KeySystemName)) { 396 requests.AppendElement(KeySystemConfigRequest{ 397 NS_ConvertUTF8toUTF16(kWidevineExperiment2KeySystemName), 398 DecryptionInfo::Hardware, aIsPrivateBrowsing}); 399 } 400 } 401 } 402 #endif 403 return KeySystemConfig::CreateKeySystemConfigs(requests); 404 } 405 406 /* static */ 407 RefPtr<GenericPromise> MediaKeySystemAccess::KeySystemSupportsInitDataType( 408 const nsAString& aKeySystem, const nsAString& aInitDataType, 409 bool aIsHardwareDecryption, bool aIsPrivateBrowsing) { 410 RefPtr<GenericPromise::Private> promise = 411 new GenericPromise::Private(__func__); 412 GetSupportedKeySystemConfigs(aKeySystem, aIsHardwareDecryption, 413 aIsPrivateBrowsing, Nothing()) 414 ->Then(GetMainThreadSerialEventTarget(), __func__, 415 [promise, initDataType = nsString{std::move(aInitDataType)}]( 416 const KeySystemConfig::SupportedConfigsPromise:: 417 ResolveOrRejectValue& aResult) { 418 if (aResult.IsResolve()) { 419 for (const auto& config : aResult.ResolveValue()) { 420 if (config.mInitDataTypes.Contains(initDataType)) { 421 promise->Resolve(true, __func__); 422 return; 423 } 424 } 425 } 426 promise->Reject(NS_ERROR_DOM_MEDIA_CDM_ERR, __func__); 427 }); 428 return promise.forget(); 429 } 430 431 enum CodecType { Audio, Video, Invalid }; 432 433 static bool CanDecryptAndDecode( 434 const nsString& aKeySystem, const nsString& aContentType, 435 CodecType aCodecType, 436 const KeySystemConfig::ContainerSupport& aContainerSupport, 437 const nsTArray<KeySystemConfig::EMECodecString>& aCodecs, 438 const Maybe<CryptoScheme>& aScheme, DecoderDoctorDiagnostics* aDiagnostics, 439 Maybe<bool> aShouldResistFingerprinting) { 440 MOZ_ASSERT(aCodecType != Invalid); 441 for (const KeySystemConfig::EMECodecString& codec : aCodecs) { 442 MOZ_ASSERT(!codec.IsEmpty()); 443 444 if (aContainerSupport.DecryptsAndDecodes(codec, aScheme)) { 445 // GMP can decrypt-and-decode this codec. 446 continue; 447 } 448 449 if (aContainerSupport.Decrypts(codec, aScheme)) { 450 IgnoredErrorResult rv; 451 MediaSource::IsTypeSupported(aContentType, aDiagnostics, rv, 452 aShouldResistFingerprinting); 453 if (!rv.Failed()) { 454 // GMP can decrypt and is allowed to return compressed samples to 455 // Gecko to decode, and Gecko has a decoder. 456 continue; 457 } 458 } 459 460 // Neither the GMP nor Gecko can both decrypt and decode. We don't 461 // support this codec. 462 463 #if defined(XP_WIN) 464 // Widevine CDM doesn't include an AAC decoder. So if WMF can't 465 // decode AAC, and a codec wasn't specified, be conservative 466 // and reject the MediaKeys request, since we assume Widevine 467 // will be used with AAC. 468 if (codec == KeySystemConfig::EME_CODEC_AAC && 469 IsWidevineKeySystem(aKeySystem) && aDiagnostics) { 470 auto pdmFactory = MakeRefPtr<PDMFactory>(); 471 if (pdmFactory->SupportsMimeType("audio/mp4a-latm"_ns).isEmpty()) { 472 aDiagnostics->SetKeySystemIssue( 473 DecoderDoctorDiagnostics::eWidevineWithNoWMF); 474 } 475 } 476 #endif 477 return false; 478 } 479 return true; 480 } 481 482 // https://w3c.github.io/encrypted-media/#dom-mediakeysystemmediacapability-encryptionscheme 483 // This convert `encryptionScheme` to the type of CryptoScheme, so that we can 484 // further check whether the scheme is supported or not in our media pipeline. 485 Maybe<CryptoScheme> ConvertEncryptionSchemeStrToScheme( 486 const nsString& aEncryptionScheme) { 487 if (DOMStringIsNull(aEncryptionScheme)) { 488 // "A missing or null value indicates that any encryption scheme is 489 // acceptable." 490 return Nothing(); 491 } 492 auto scheme = StringToCryptoScheme(aEncryptionScheme); 493 return Some(scheme); 494 } 495 496 static bool ToSessionType(const nsAString& aSessionType, 497 MediaKeySessionType& aOutType) { 498 Maybe<MediaKeySessionType> type = 499 StringToEnum<MediaKeySessionType>(aSessionType); 500 if (type.isNothing()) { 501 return false; 502 } 503 aOutType = type.value(); 504 return true; 505 } 506 507 // 5.1.1 Is persistent session type? 508 static bool IsPersistentSessionType(MediaKeySessionType aSessionType) { 509 return aSessionType == MediaKeySessionType::Persistent_license; 510 } 511 512 static bool ContainsSessionType( 513 const nsTArray<KeySystemConfig::SessionType>& aTypes, 514 const MediaKeySessionType& aSessionType) { 515 return (aSessionType == MediaKeySessionType::Persistent_license && 516 aTypes.Contains(KeySystemConfig::SessionType::PersistentLicense)) || 517 (aSessionType == MediaKeySessionType::Temporary && 518 aTypes.Contains(KeySystemConfig::SessionType::Temporary)); 519 } 520 521 CodecType GetMajorType(const MediaMIMEType& aMIMEType) { 522 if (aMIMEType.HasAudioMajorType()) { 523 return Audio; 524 } 525 if (aMIMEType.HasVideoMajorType()) { 526 return Video; 527 } 528 return Invalid; 529 } 530 531 static CodecType GetCodecType(const KeySystemConfig::EMECodecString& aCodec) { 532 if (aCodec.Equals(KeySystemConfig::EME_CODEC_AAC) || 533 aCodec.Equals(KeySystemConfig::EME_CODEC_OPUS) || 534 aCodec.Equals(KeySystemConfig::EME_CODEC_VORBIS) || 535 aCodec.Equals(KeySystemConfig::EME_CODEC_FLAC)) { 536 return Audio; 537 } 538 if (aCodec.Equals(KeySystemConfig::EME_CODEC_H264) || 539 aCodec.Equals(KeySystemConfig::EME_CODEC_AV1) || 540 aCodec.Equals(KeySystemConfig::EME_CODEC_VP8) || 541 aCodec.Equals(KeySystemConfig::EME_CODEC_VP9) || 542 aCodec.Equals(KeySystemConfig::EME_CODEC_HEVC)) { 543 return Video; 544 } 545 return Invalid; 546 } 547 548 static bool AllCodecsOfType( 549 const nsTArray<KeySystemConfig::EMECodecString>& aCodecs, 550 const CodecType aCodecType) { 551 for (const KeySystemConfig::EMECodecString& codec : aCodecs) { 552 if (GetCodecType(codec) != aCodecType) { 553 return false; 554 } 555 } 556 return true; 557 } 558 559 static bool IsParameterUnrecognized(const nsAString& aContentType) { 560 nsAutoString contentType(aContentType); 561 contentType.StripWhitespace(); 562 563 nsTArray<nsString> params; 564 nsAString::const_iterator start, end, semicolon, equalSign; 565 contentType.BeginReading(start); 566 contentType.EndReading(end); 567 semicolon = start; 568 // Find any substring between ';' & '='. 569 while (semicolon != end) { 570 if (FindCharInReadable(';', semicolon, end)) { 571 equalSign = ++semicolon; 572 if (FindCharInReadable('=', equalSign, end)) { 573 params.AppendElement(Substring(semicolon, equalSign)); 574 semicolon = equalSign; 575 } 576 } 577 } 578 579 for (auto param : params) { 580 if (!param.LowerCaseEqualsLiteral("codecs") && 581 !param.LowerCaseEqualsLiteral("profiles")) { 582 return true; 583 } 584 } 585 return false; 586 } 587 588 // 3.2.2.3 Get Supported Capabilities for Audio/Video Type 589 // https://w3c.github.io/encrypted-media/#get-supported-capabilities-for-audio-video-type 590 static Sequence<MediaKeySystemMediaCapability> GetSupportedCapabilities( 591 const CodecType aCodecType, 592 const nsTArray<MediaKeySystemMediaCapability>& aRequestedCapabilities, 593 const MediaKeySystemConfiguration& aPartialConfig, 594 const KeySystemConfig& aKeySystem, DecoderDoctorDiagnostics* aDiagnostics, 595 const Document* aDocument) { 596 // Let local accumulated configuration be a local copy of partial 597 // configuration. (Note: It's not necessary for us to maintain a local copy, 598 // as we don't need to test whether capabilites from previous calls to this 599 // algorithm work with the capabilities currently being considered in this 600 // call. ) 601 602 // Let supported media capabilities be an empty sequence of 603 // MediaKeySystemMediaCapability dictionaries. 604 Sequence<MediaKeySystemMediaCapability> supportedCapabilities; 605 606 // For each requested media capability in requested media capabilities: 607 for (const MediaKeySystemMediaCapability& capabilities : 608 aRequestedCapabilities) { 609 // Let content type be requested media capability's contentType member. 610 const nsString& contentTypeString = capabilities.mContentType; 611 // Let robustness be requested media capability's robustness member. 612 const nsString& robustness = capabilities.mRobustness; 613 // Optional encryption scheme extension, see 614 // https://github.com/WICG/encrypted-media-encryption-scheme/blob/master/explainer.md 615 // This will only be exposed to JS if 616 // media.eme.encrypted-media-encryption-scheme.enabled is preffed on. 617 const nsString encryptionScheme = capabilities.mEncryptionScheme; 618 // If content type is the empty string, return null. 619 if (contentTypeString.IsEmpty()) { 620 EME_LOG( 621 "MediaKeySystemConfiguration (label='%s') " 622 "MediaKeySystemMediaCapability('%s','%s','%s') rejected; " 623 "audio or video capability has empty contentType.", 624 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), 625 NS_ConvertUTF16toUTF8(contentTypeString).get(), 626 NS_ConvertUTF16toUTF8(robustness).get(), 627 NS_ConvertUTF16toUTF8(encryptionScheme).get()); 628 return Sequence<MediaKeySystemMediaCapability>(); 629 } 630 // If content type is an invalid or unrecognized MIME type, continue 631 // to the next iteration. 632 Maybe<MediaContainerType> maybeContainerType = 633 MakeMediaContainerType(contentTypeString); 634 if (!maybeContainerType) { 635 EME_LOG( 636 "MediaKeySystemConfiguration (label='%s') " 637 "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; " 638 "failed to parse contentTypeString as MIME type.", 639 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), 640 NS_ConvertUTF16toUTF8(contentTypeString).get(), 641 NS_ConvertUTF16toUTF8(robustness).get(), 642 NS_ConvertUTF16toUTF8(encryptionScheme).get()); 643 continue; 644 } 645 const MediaContainerType& containerType = *maybeContainerType; 646 bool invalid = false; 647 nsTArray<KeySystemConfig::EMECodecString> codecs; 648 for (const auto& codecString : 649 containerType.ExtendedType().Codecs().Range()) { 650 KeySystemConfig::EMECodecString emeCodec = 651 ToEMEAPICodecString(nsString(codecString)); 652 if (emeCodec.IsEmpty()) { 653 invalid = true; 654 EME_LOG( 655 "MediaKeySystemConfiguration (label='%s') " 656 "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; " 657 "'%s' is an invalid codec string.", 658 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), 659 NS_ConvertUTF16toUTF8(contentTypeString).get(), 660 NS_ConvertUTF16toUTF8(robustness).get(), 661 NS_ConvertUTF16toUTF8(encryptionScheme).get(), 662 NS_ConvertUTF16toUTF8(codecString).get()); 663 break; 664 } 665 codecs.AppendElement(emeCodec); 666 } 667 if (invalid) { 668 continue; 669 } 670 671 // If the user agent does not support container, continue to the next 672 // iteration. The case-sensitivity of string comparisons is determined by 673 // the appropriate RFC. (Note: Per RFC 6838 [RFC6838], "Both top-level type 674 // and subtype names are case-insensitive."'. We're using 675 // nsContentTypeParser and that is case-insensitive and converts all its 676 // parameter outputs to lower case.) 677 const bool supportedInMP4 = 678 MP4Decoder::IsSupportedType(containerType, aDiagnostics); 679 if (supportedInMP4 && !aKeySystem.mMP4.IsSupported()) { 680 EME_LOG( 681 "MediaKeySystemConfiguration (label='%s') " 682 "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; " 683 "MP4 requested but unsupported.", 684 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), 685 NS_ConvertUTF16toUTF8(contentTypeString).get(), 686 NS_ConvertUTF16toUTF8(robustness).get(), 687 NS_ConvertUTF16toUTF8(encryptionScheme).get()); 688 continue; 689 } 690 const bool isWebM = WebMDecoder::IsSupportedType(containerType); 691 if (isWebM && !aKeySystem.mWebM.IsSupported()) { 692 EME_LOG( 693 "MediaKeySystemConfiguration (label='%s') " 694 "MediaKeySystemMediaCapability('%s','%s,'%s') unsupported; " 695 "WebM requested but unsupported.", 696 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), 697 NS_ConvertUTF16toUTF8(contentTypeString).get(), 698 NS_ConvertUTF16toUTF8(robustness).get(), 699 NS_ConvertUTF16toUTF8(encryptionScheme).get()); 700 continue; 701 } 702 if (!supportedInMP4 && !isWebM) { 703 EME_LOG( 704 "MediaKeySystemConfiguration (label='%s') " 705 "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; " 706 "Unsupported or unrecognized container requested.", 707 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), 708 NS_ConvertUTF16toUTF8(contentTypeString).get(), 709 NS_ConvertUTF16toUTF8(robustness).get(), 710 NS_ConvertUTF16toUTF8(encryptionScheme).get()); 711 continue; 712 } 713 714 // Let parameters be the RFC 6381[RFC6381] parameters, if any, specified by 715 // content type. 716 // If the user agent does not recognize one or more parameters, continue to 717 // the next iteration. 718 if (IsParameterUnrecognized(contentTypeString)) { 719 continue; 720 } 721 722 // Let media types be the set of codecs and codec constraints specified by 723 // parameters. The case-sensitivity of string comparisons is determined by 724 // the appropriate RFC or other specification. 725 // (Note: codecs array is 'parameter'). 726 727 // If media types is empty: 728 if (codecs.IsEmpty()) { 729 // Log deprecation warning to encourage authors to not do this! 730 DeprecationWarningLog(aDocument, "MediaEMENoCodecsDeprecatedWarning"); 731 // TODO: Remove this once we're sure it doesn't break the web. 732 // If container normatively implies a specific set of codecs and codec 733 // constraints: Let parameters be that set. 734 if (supportedInMP4) { 735 if (aCodecType == Audio) { 736 codecs.AppendElement(KeySystemConfig::EME_CODEC_AAC); 737 } else if (aCodecType == Video) { 738 codecs.AppendElement(KeySystemConfig::EME_CODEC_H264); 739 } 740 } else if (isWebM) { 741 if (aCodecType == Audio) { 742 codecs.AppendElement(KeySystemConfig::EME_CODEC_VORBIS); 743 } else if (aCodecType == Video) { 744 codecs.AppendElement(KeySystemConfig::EME_CODEC_VP8); 745 } 746 } 747 // Otherwise: Continue to the next iteration. 748 // (Note: all containers we support have implied codecs, so don't continue 749 // here.) 750 } 751 752 // If container type is not strictly a audio/video type, continue to the 753 // next iteration. 754 const auto majorType = GetMajorType(containerType.Type()); 755 if (majorType == Invalid) { 756 EME_LOG( 757 "MediaKeySystemConfiguration (label='%s') " 758 "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; " 759 "MIME type is not an audio or video MIME type.", 760 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), 761 NS_ConvertUTF16toUTF8(contentTypeString).get(), 762 NS_ConvertUTF16toUTF8(robustness).get(), 763 NS_ConvertUTF16toUTF8(encryptionScheme).get()); 764 continue; 765 } 766 if (majorType != aCodecType || !AllCodecsOfType(codecs, aCodecType)) { 767 EME_LOG( 768 "MediaKeySystemConfiguration (label='%s') " 769 "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; " 770 "MIME type mixes audio codecs in video capabilities " 771 "or video codecs in audio capabilities.", 772 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), 773 NS_ConvertUTF16toUTF8(contentTypeString).get(), 774 NS_ConvertUTF16toUTF8(robustness).get(), 775 NS_ConvertUTF16toUTF8(encryptionScheme).get()); 776 continue; 777 } 778 // If encryption scheme is non-null and is not recognized or not supported 779 // by implementation, continue to the next iteration. 780 const auto scheme = ConvertEncryptionSchemeStrToScheme(encryptionScheme); 781 if (scheme && *scheme == CryptoScheme::None) { 782 EME_LOG( 783 "MediaKeySystemConfiguration (label='%s') " 784 "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; " 785 "unsupported scheme string.", 786 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), 787 NS_ConvertUTF16toUTF8(contentTypeString).get(), 788 NS_ConvertUTF16toUTF8(robustness).get(), 789 NS_ConvertUTF16toUTF8(encryptionScheme).get()); 790 continue; 791 } 792 // If robustness is not the empty string and contains an unrecognized 793 // value or a value not supported by implementation, continue to the 794 // next iteration. String comparison is case-sensitive. 795 if (!robustness.IsEmpty()) { 796 if (majorType == Audio && 797 !aKeySystem.mAudioRobustness.Contains(robustness)) { 798 EME_LOG( 799 "MediaKeySystemConfiguration (label='%s') " 800 "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; " 801 "unsupported robustness string.", 802 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), 803 NS_ConvertUTF16toUTF8(contentTypeString).get(), 804 NS_ConvertUTF16toUTF8(robustness).get(), 805 NS_ConvertUTF16toUTF8(encryptionScheme).get()); 806 continue; 807 } 808 if (majorType == Video && 809 !aKeySystem.mVideoRobustness.Contains(robustness)) { 810 EME_LOG( 811 "MediaKeySystemConfiguration (label='%s') " 812 "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; " 813 "unsupported robustness string.", 814 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), 815 NS_ConvertUTF16toUTF8(contentTypeString).get(), 816 NS_ConvertUTF16toUTF8(robustness).get(), 817 NS_ConvertUTF16toUTF8(encryptionScheme).get()); 818 continue; 819 } 820 // Note: specified robustness requirements are satisfied. 821 } 822 823 // If the user agent and implementation definitely support playback of 824 // encrypted media data for the combination of container, media types, 825 // robustness and local accumulated configuration in combination with 826 // restrictions... 827 const auto& containerSupport = 828 supportedInMP4 ? aKeySystem.mMP4 : aKeySystem.mWebM; 829 Maybe<bool> shouldResistFingerprinting = 830 aDocument ? Some(aDocument->ShouldResistFingerprinting( 831 RFPTarget::MediaCapabilities)) 832 : Nothing(); 833 if (!CanDecryptAndDecode(aKeySystem.mKeySystem, contentTypeString, 834 majorType, containerSupport, codecs, scheme, 835 aDiagnostics, shouldResistFingerprinting)) { 836 EME_LOG( 837 "MediaKeySystemConfiguration (label='%s') " 838 "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; " 839 "codec unsupported by CDM requested.", 840 NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(), 841 NS_ConvertUTF16toUTF8(contentTypeString).get(), 842 NS_ConvertUTF16toUTF8(robustness).get(), 843 NS_ConvertUTF16toUTF8(encryptionScheme).get()); 844 continue; 845 } 846 847 // ... add requested media capability to supported media capabilities. 848 if (!supportedCapabilities.AppendElement(capabilities, mozilla::fallible)) { 849 NS_WARNING("GetSupportedCapabilities: Malloc failure"); 850 return Sequence<MediaKeySystemMediaCapability>(); 851 } 852 853 // Note: omitting steps 3.13.2, our robustness is not sophisticated enough 854 // to require considering all requirements together. 855 } 856 return supportedCapabilities; 857 } 858 859 // "Get Supported Configuration and Consent" algorithm, steps 4-7 for 860 // distinctive identifier, and steps 8-11 for persistent state. The steps 861 // are the same for both requirements/features, so we factor them out into 862 // a single function. 863 static bool CheckRequirement( 864 const MediaKeysRequirement aRequirement, 865 const KeySystemConfig::Requirement aKeySystemRequirement, 866 MediaKeysRequirement& aOutRequirement) { 867 // Let requirement be the value of candidate configuration's member. 868 MediaKeysRequirement requirement = aRequirement; 869 // If requirement is "optional" and feature is not allowed according to 870 // restrictions, set requirement to "not-allowed". 871 if (aRequirement == MediaKeysRequirement::Optional && 872 aKeySystemRequirement == KeySystemConfig::Requirement::NotAllowed) { 873 requirement = MediaKeysRequirement::Not_allowed; 874 } 875 876 // Follow the steps for requirement from the following list: 877 switch (requirement) { 878 case MediaKeysRequirement::Required: { 879 // If the implementation does not support use of requirement in 880 // combination with accumulated configuration and restrictions, return 881 // NotSupported. 882 if (aKeySystemRequirement == KeySystemConfig::Requirement::NotAllowed) { 883 return false; 884 } 885 break; 886 } 887 case MediaKeysRequirement::Optional: { 888 // Continue with the following steps. 889 break; 890 } 891 case MediaKeysRequirement::Not_allowed: { 892 // If the implementation requires use of feature in combination with 893 // accumulated configuration and restrictions, return NotSupported. 894 if (aKeySystemRequirement == KeySystemConfig::Requirement::Required) { 895 return false; 896 } 897 break; 898 } 899 default: { 900 return false; 901 } 902 } 903 904 // Set the requirement member of accumulated configuration to equal 905 // calculated requirement. 906 aOutRequirement = requirement; 907 908 return true; 909 } 910 911 // 3.1.1.2, step 12 912 // Follow the steps for the first matching condition from the following list: 913 // If the sessionTypes member is present in candidate configuration. 914 // Let session types be candidate configuration's sessionTypes member. 915 // Otherwise let session types be ["temporary"]. 916 // Note: This returns an empty array on malloc failure. 917 static Sequence<nsString> UnboxSessionTypes( 918 const Optional<Sequence<nsString>>& aSessionTypes) { 919 Sequence<nsString> sessionTypes; 920 if (aSessionTypes.WasPassed()) { 921 sessionTypes = aSessionTypes.Value(); 922 } else { 923 // Note: fallible. Results in an empty array. 924 (void)sessionTypes.AppendElement(ToString(MediaKeySessionType::Temporary), 925 mozilla::fallible); 926 } 927 return sessionTypes; 928 } 929 930 // 3.1.1.2 Get Supported Configuration and Consent 931 static bool GetSupportedConfig(const KeySystemConfig& aKeySystem, 932 const MediaKeySystemConfiguration& aCandidate, 933 MediaKeySystemConfiguration& aOutConfig, 934 DecoderDoctorDiagnostics* aDiagnostics, 935 const Document* aDocument) { 936 EME_LOG("Compare implementation '%s'\n with request '%s'", 937 NS_ConvertUTF16toUTF8(aKeySystem.GetDebugInfo()).get(), 938 MediaKeySystemAccess::ToCString(aCandidate).get()); 939 // Let accumulated configuration be a new MediaKeySystemConfiguration 940 // dictionary. 941 MediaKeySystemConfiguration config; 942 // Set the label member of accumulated configuration to equal the label member 943 // of candidate configuration. 944 config.mLabel = aCandidate.mLabel; 945 // If the initDataTypes member of candidate configuration is non-empty, run 946 // the following steps: 947 if (!aCandidate.mInitDataTypes.IsEmpty()) { 948 // Let supported types be an empty sequence of DOMStrings. 949 nsTArray<nsString> supportedTypes; 950 // For each value in candidate configuration's initDataTypes member: 951 for (const nsString& initDataType : aCandidate.mInitDataTypes) { 952 // Let initDataType be the value. 953 // If the implementation supports generating requests based on 954 // initDataType, add initDataType to supported types. String comparison is 955 // case-sensitive. The empty string is never supported. 956 if (aKeySystem.mInitDataTypes.Contains(initDataType)) { 957 supportedTypes.AppendElement(initDataType); 958 } 959 } 960 // If supported types is empty, return NotSupported. 961 if (supportedTypes.IsEmpty()) { 962 EME_LOG( 963 "MediaKeySystemConfiguration (label='%s') rejected; " 964 "no supported initDataTypes provided.", 965 NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); 966 return false; 967 } 968 // Set the initDataTypes member of accumulated configuration to supported 969 // types. 970 if (!config.mInitDataTypes.Assign(supportedTypes)) { 971 return false; 972 } 973 } 974 975 if (!CheckRequirement(aCandidate.mDistinctiveIdentifier, 976 aKeySystem.mDistinctiveIdentifier, 977 config.mDistinctiveIdentifier)) { 978 EME_LOG( 979 "MediaKeySystemConfiguration (label='%s') rejected; " 980 "distinctiveIdentifier requirement not satisfied.", 981 NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); 982 return false; 983 } 984 985 if (!CheckRequirement(aCandidate.mPersistentState, 986 aKeySystem.mPersistentState, config.mPersistentState)) { 987 EME_LOG( 988 "MediaKeySystemConfiguration (label='%s') rejected; " 989 "persistentState requirement not satisfied.", 990 NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); 991 return false; 992 } 993 994 Sequence<nsString> sessionTypes(UnboxSessionTypes(aCandidate.mSessionTypes)); 995 if (sessionTypes.IsEmpty()) { 996 // Malloc failure. 997 return false; 998 } 999 1000 // For each value in session types: 1001 for (const auto& sessionTypeString : sessionTypes) { 1002 // Let session type be the value. 1003 MediaKeySessionType sessionType; 1004 if (!ToSessionType(sessionTypeString, sessionType)) { 1005 // (Assume invalid sessionType is unsupported as per steps below). 1006 EME_LOG( 1007 "MediaKeySystemConfiguration (label='%s') rejected; " 1008 "invalid session type specified.", 1009 NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); 1010 return false; 1011 } 1012 // If accumulated configuration's persistentState value is "not-allowed" 1013 // and the Is persistent session type? algorithm returns true for session 1014 // type return NotSupported. 1015 if (config.mPersistentState == MediaKeysRequirement::Not_allowed && 1016 IsPersistentSessionType(sessionType)) { 1017 EME_LOG( 1018 "MediaKeySystemConfiguration (label='%s') rejected; " 1019 "persistent session requested but keysystem doesn't" 1020 "support persistent state.", 1021 NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); 1022 return false; 1023 } 1024 // If the implementation does not support session type in combination 1025 // with accumulated configuration and restrictions for other reasons, 1026 // return NotSupported. 1027 if (!ContainsSessionType(aKeySystem.mSessionTypes, sessionType)) { 1028 EME_LOG( 1029 "MediaKeySystemConfiguration (label='%s') rejected; " 1030 "session type '%s' unsupported by keySystem.", 1031 NS_ConvertUTF16toUTF8(aCandidate.mLabel).get(), 1032 NS_ConvertUTF16toUTF8(sessionTypeString).get()); 1033 return false; 1034 } 1035 // If accumulated configuration's persistentState value is "optional" 1036 // and the result of running the Is persistent session type? algorithm 1037 // on session type is true, change accumulated configuration's 1038 // persistentState value to "required". 1039 if (config.mPersistentState == MediaKeysRequirement::Optional && 1040 IsPersistentSessionType(sessionType)) { 1041 config.mPersistentState = MediaKeysRequirement::Required; 1042 } 1043 } 1044 // Set the sessionTypes member of accumulated configuration to session types. 1045 config.mSessionTypes.Construct(std::move(sessionTypes)); 1046 1047 // If the videoCapabilities and audioCapabilities members in candidate 1048 // configuration are both empty, return NotSupported. 1049 if (aCandidate.mAudioCapabilities.IsEmpty() && 1050 aCandidate.mVideoCapabilities.IsEmpty()) { 1051 // TODO: Most sites using EME still don't pass capabilities, so we 1052 // can't reject on it yet without breaking them. So add this later. 1053 // Log deprecation warning to encourage authors to not do this! 1054 DeprecationWarningLog(aDocument, "MediaEMENoCapabilitiesDeprecatedWarning"); 1055 } 1056 1057 // If the videoCapabilities member in candidate configuration is non-empty: 1058 if (!aCandidate.mVideoCapabilities.IsEmpty()) { 1059 // Let video capabilities be the result of executing the Get Supported 1060 // Capabilities for Audio/Video Type algorithm on Video, candidate 1061 // configuration's videoCapabilities member, accumulated configuration, 1062 // and restrictions. 1063 Sequence<MediaKeySystemMediaCapability> caps = 1064 GetSupportedCapabilities(Video, aCandidate.mVideoCapabilities, config, 1065 aKeySystem, aDiagnostics, aDocument); 1066 // If video capabilities is null, return NotSupported. 1067 if (caps.IsEmpty()) { 1068 EME_LOG( 1069 "MediaKeySystemConfiguration (label='%s') rejected; " 1070 "no supported video capabilities.", 1071 NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); 1072 return false; 1073 } 1074 // Set the videoCapabilities member of accumulated configuration to video 1075 // capabilities. 1076 config.mVideoCapabilities = std::move(caps); 1077 } else { 1078 // Otherwise: 1079 // Set the videoCapabilities member of accumulated configuration to an empty 1080 // sequence. 1081 } 1082 1083 // If the audioCapabilities member in candidate configuration is non-empty: 1084 if (!aCandidate.mAudioCapabilities.IsEmpty()) { 1085 // Let audio capabilities be the result of executing the Get Supported 1086 // Capabilities for Audio/Video Type algorithm on Audio, candidate 1087 // configuration's audioCapabilities member, accumulated configuration, and 1088 // restrictions. 1089 Sequence<MediaKeySystemMediaCapability> caps = 1090 GetSupportedCapabilities(Audio, aCandidate.mAudioCapabilities, config, 1091 aKeySystem, aDiagnostics, aDocument); 1092 // If audio capabilities is null, return NotSupported. 1093 if (caps.IsEmpty()) { 1094 EME_LOG( 1095 "MediaKeySystemConfiguration (label='%s') rejected; " 1096 "no supported audio capabilities.", 1097 NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); 1098 return false; 1099 } 1100 // Set the audioCapabilities member of accumulated configuration to audio 1101 // capabilities. 1102 config.mAudioCapabilities = std::move(caps); 1103 } else { 1104 // Otherwise: 1105 // Set the audioCapabilities member of accumulated configuration to an empty 1106 // sequence. 1107 } 1108 1109 // If accumulated configuration's distinctiveIdentifier value is "optional", 1110 // follow the steps for the first matching condition from the following list: 1111 if (config.mDistinctiveIdentifier == MediaKeysRequirement::Optional) { 1112 // If the implementation requires use Distinctive Identifier(s) or 1113 // Distinctive Permanent Identifier(s) for any of the combinations 1114 // in accumulated configuration 1115 if (aKeySystem.mDistinctiveIdentifier == 1116 KeySystemConfig::Requirement::Required) { 1117 // Change accumulated configuration's distinctiveIdentifier value to 1118 // "required". 1119 config.mDistinctiveIdentifier = MediaKeysRequirement::Required; 1120 } else { 1121 // Otherwise, change accumulated configuration's distinctiveIdentifier 1122 // value to "not-allowed". 1123 config.mDistinctiveIdentifier = MediaKeysRequirement::Not_allowed; 1124 } 1125 } 1126 1127 // If accumulated configuration's persistentState value is "optional", follow 1128 // the steps for the first matching condition from the following list: 1129 if (config.mPersistentState == MediaKeysRequirement::Optional) { 1130 // If the implementation requires persisting state for any of the 1131 // combinations in accumulated configuration 1132 if (aKeySystem.mPersistentState == KeySystemConfig::Requirement::Required) { 1133 // Change accumulated configuration's persistentState value to "required". 1134 config.mPersistentState = MediaKeysRequirement::Required; 1135 } else { 1136 // Otherwise, change accumulated configuration's persistentState 1137 // value to "not-allowed". 1138 config.mPersistentState = MediaKeysRequirement::Not_allowed; 1139 } 1140 } 1141 1142 // Note: Omitting steps 20-22. We don't ask for consent. 1143 1144 #if defined(XP_WIN) 1145 // Widevine CDM doesn't include an AAC decoder. So if WMF can't decode AAC, 1146 // and a codec wasn't specified, be conservative and reject the MediaKeys 1147 // request. 1148 if (IsWidevineKeySystem(aKeySystem.mKeySystem) && 1149 (aCandidate.mAudioCapabilities.IsEmpty() || 1150 aCandidate.mVideoCapabilities.IsEmpty())) { 1151 auto pdmFactory = MakeRefPtr<PDMFactory>(); 1152 if (pdmFactory->SupportsMimeType("audio/mp4a-latm"_ns).isEmpty()) { 1153 if (aDiagnostics) { 1154 aDiagnostics->SetKeySystemIssue( 1155 DecoderDoctorDiagnostics::eWidevineWithNoWMF); 1156 } 1157 EME_LOG( 1158 "MediaKeySystemConfiguration (label='%s') rejected; " 1159 "WMF required for Widevine decoding, but it's not available.", 1160 NS_ConvertUTF16toUTF8(aCandidate.mLabel).get()); 1161 return false; 1162 } 1163 } 1164 #endif 1165 1166 // Return accumulated configuration. 1167 aOutConfig = config; 1168 1169 return true; 1170 } 1171 1172 /* static */ 1173 RefPtr<KeySystemConfig::KeySystemConfigPromise> 1174 MediaKeySystemAccess::GetSupportedConfig(MediaKeySystemAccessRequest* aRequest, 1175 bool aIsPrivateBrowsing, 1176 const Document* aDocument) { 1177 nsTArray<KeySystemConfig> implementations; 1178 const bool containsHardwareDecryptionConfig = 1179 CheckIfHarewareDRMConfigExists(aRequest->mConfigs) || 1180 DoesKeySystemSupportHardwareDecryption(aRequest->mKeySystem); 1181 1182 RefPtr<KeySystemConfig::KeySystemConfigPromise::Private> promise = 1183 new KeySystemConfig::KeySystemConfigPromise::Private(__func__); 1184 GetSupportedKeySystemConfigs(aRequest->mKeySystem, 1185 containsHardwareDecryptionConfig, 1186 aIsPrivateBrowsing, GetOrigin(aDocument)) 1187 ->Then(GetMainThreadSerialEventTarget(), __func__, 1188 [promise, aRequest, document = RefPtr<const Document>{aDocument}]( 1189 const KeySystemConfig::SupportedConfigsPromise:: 1190 ResolveOrRejectValue& aResult) { 1191 if (aResult.IsResolve()) { 1192 MediaKeySystemConfiguration outConfig; 1193 for (const auto& implementation : aResult.ResolveValue()) { 1194 for (const MediaKeySystemConfiguration& candidate : 1195 aRequest->mConfigs) { 1196 if (mozilla::dom::GetSupportedConfig( 1197 implementation, candidate, outConfig, 1198 &aRequest->mDiagnostics, document)) { 1199 promise->Resolve(std::move(outConfig), __func__); 1200 return; 1201 } 1202 } 1203 } 1204 } 1205 promise->Reject(false, __func__); 1206 }); 1207 return promise.forget(); 1208 } 1209 1210 /* static */ 1211 void MediaKeySystemAccess::NotifyObservers(nsPIDOMWindowInner* aWindow, 1212 const nsAString& aKeySystem, 1213 MediaKeySystemStatus aStatus) { 1214 RequestMediaKeySystemAccessNotification data; 1215 data.mKeySystem = aKeySystem; 1216 data.mStatus = aStatus; 1217 nsAutoString json; 1218 data.ToJSON(json); 1219 EME_LOG("MediaKeySystemAccess::NotifyObservers() %s", 1220 NS_ConvertUTF16toUTF8(json).get()); 1221 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 1222 if (obs) { 1223 obs->NotifyObservers(aWindow, MediaKeys::kMediaKeysRequestTopic, 1224 json.get()); 1225 } 1226 } 1227 1228 static nsCString ToCString(const nsString& aString) { 1229 nsCString str("\""); 1230 str.Append(NS_ConvertUTF16toUTF8(aString)); 1231 str.AppendLiteral("\""); 1232 return str; 1233 } 1234 1235 static nsCString ToCString(const MediaKeysRequirement aValue) { 1236 nsCString str("\""); 1237 str.AppendASCII(GetEnumString(aValue)); 1238 str.AppendLiteral("\""); 1239 return str; 1240 } 1241 1242 static nsCString ToCString(const MediaKeySystemMediaCapability& aValue) { 1243 nsCString str; 1244 str.AppendLiteral(R"({"contentType":")"); 1245 // Escape any quotes in the content type 1246 nsString escapedContentType(aValue.mContentType); 1247 escapedContentType.ReplaceSubstring(u"\"", u"\\\""); 1248 str.Append(NS_ConvertUTF16toUTF8(escapedContentType)); 1249 str.AppendLiteral(R"(", "robustness":)"); 1250 str.Append(ToCString(aValue.mRobustness)); 1251 str.AppendLiteral(R"(, "encryptionScheme":)"); 1252 str.Append(ToCString(aValue.mEncryptionScheme)); 1253 str.AppendLiteral("}"); 1254 return str; 1255 } 1256 1257 template <class Type> 1258 nsCString ToCString(const Sequence<Type>& aSequence) { 1259 nsCString str; 1260 str.AppendLiteral("["); 1261 StringJoinAppend(str, ","_ns, aSequence, 1262 [](nsACString& dest, const Type& element) { 1263 dest.Append(ToCString(element)); 1264 }); 1265 str.AppendLiteral("]"); 1266 return str; 1267 } 1268 1269 template <> 1270 nsCString ToCString(const Sequence<MediaKeySystemConfiguration>& aSequence) { 1271 nsCString str; 1272 str.AppendLiteral("["); 1273 StringJoinAppend( 1274 str, ","_ns, aSequence, 1275 [](nsACString& dest, const MediaKeySystemConfiguration& element) { 1276 dest.Append(MediaKeySystemAccess::ToCString(element)); 1277 }); 1278 str.AppendLiteral("]"); 1279 return str; 1280 } 1281 1282 template <class Type> 1283 nsCString ToCString(const Optional<Sequence<Type>>& aOptional) { 1284 nsCString str; 1285 if (aOptional.WasPassed()) { 1286 str.Append(ToCString(aOptional.Value())); 1287 } else { 1288 str.AppendLiteral("[]"); 1289 } 1290 return str; 1291 } 1292 1293 template <> 1294 nsCString ToCString( 1295 const Optional<Sequence<MediaKeySystemConfiguration>>& aOptional) { 1296 nsCString str; 1297 if (aOptional.WasPassed()) { 1298 str.Append(MediaKeySystemAccess::ToCString(aOptional.Value())); 1299 } else { 1300 str.AppendLiteral("[]"); 1301 } 1302 return str; 1303 } 1304 1305 /* static */ 1306 nsCString MediaKeySystemAccess::ToCString( 1307 const MediaKeySystemConfiguration& aConfig) { 1308 nsCString str; 1309 str.AppendLiteral(R"({"label":)"); 1310 str.Append(mozilla::dom::ToCString(aConfig.mLabel)); 1311 1312 str.AppendLiteral(R"(, "initDataTypes":)"); 1313 str.Append(mozilla::dom::ToCString(aConfig.mInitDataTypes)); 1314 1315 str.AppendLiteral(R"(, "audioCapabilities":)"); 1316 str.Append(mozilla::dom::ToCString(aConfig.mAudioCapabilities)); 1317 1318 str.AppendLiteral(R"(, "videoCapabilities":)"); 1319 str.Append(mozilla::dom::ToCString(aConfig.mVideoCapabilities)); 1320 1321 str.AppendLiteral(R"(, "distinctiveIdentifier":)"); 1322 str.Append(mozilla::dom::ToCString(aConfig.mDistinctiveIdentifier)); 1323 1324 str.AppendLiteral(R"(, "persistentState":)"); 1325 str.Append(mozilla::dom::ToCString(aConfig.mPersistentState)); 1326 1327 str.AppendLiteral(R"(, "sessionTypes":)"); 1328 str.Append(mozilla::dom::ToCString(aConfig.mSessionTypes)); 1329 1330 str.AppendLiteral("}"); 1331 1332 return str; 1333 } 1334 1335 /* static */ 1336 nsCString MediaKeySystemAccess::ToCString( 1337 const Sequence<MediaKeySystemConfiguration>& aConfig) { 1338 return mozilla::dom::ToCString(aConfig); 1339 } 1340 1341 #undef LOG 1342 1343 } // namespace mozilla::dom