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 #include "mozilla/dom/MediaKeySystemAccessBinding.h"
9 #include "mozilla/dom/MediaKeySession.h"
10 #include "mozilla/Preferences.h"
11 #include "MediaContainerType.h"
12 #include "MediaPrefs.h"
13 #include "nsMimeTypes.h"
14 #ifdef XP_WIN
15 #include "WMFDecoderModule.h"
16 #endif
17 #include "nsContentCID.h"
18 #include "nsServiceManagerUtils.h"
19 #include "mozIGeckoMediaPluginService.h"
20 #include "VideoUtils.h"
21 #include "mozilla/Services.h"
22 #include "nsIObserverService.h"
23 #include "mozilla/EMEUtils.h"
24 #include "GMPUtils.h"
25 #include "nsAppDirectoryServiceDefs.h"
26 #include "nsDirectoryServiceUtils.h"
27 #include "nsDirectoryServiceDefs.h"
28 #include "nsXULAppAPI.h"
29 #include "DecoderDoctorDiagnostics.h"
30 #include "WebMDecoder.h"
31 #include "mozilla/StaticPtr.h"
32 #include "mozilla/ClearOnShutdown.h"
33 #include "nsUnicharUtils.h"
34 #include "mozilla/dom/MediaSource.h"
35 #include "DecoderTraits.h"
36 #ifdef MOZ_WIDGET_ANDROID
37 #include "FennecJNIWrappers.h"
38 #endif
39 #include <functional>
40 
41 namespace mozilla {
42 namespace dom {
43 
44 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess, mParent)
45 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
46 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
47 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccess)
48   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
49   NS_INTERFACE_MAP_ENTRY(nsISupports)
50 NS_INTERFACE_MAP_END
51 
52 static nsCString ToCString(const MediaKeySystemConfiguration& aConfig);
53 
MediaKeySystemAccess(nsPIDOMWindowInner * aParent,const nsAString & aKeySystem,const MediaKeySystemConfiguration & aConfig)54 MediaKeySystemAccess::MediaKeySystemAccess(
55     nsPIDOMWindowInner* aParent, const nsAString& aKeySystem,
56     const MediaKeySystemConfiguration& aConfig)
57     : mParent(aParent), mKeySystem(aKeySystem), mConfig(aConfig) {
58   EME_LOG("Created MediaKeySystemAccess for keysystem=%s config=%s",
59           NS_ConvertUTF16toUTF8(mKeySystem).get(),
60           mozilla::dom::ToCString(mConfig).get());
61 }
62 
~MediaKeySystemAccess()63 MediaKeySystemAccess::~MediaKeySystemAccess() {}
64 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)65 JSObject* MediaKeySystemAccess::WrapObject(JSContext* aCx,
66                                            JS::Handle<JSObject*> aGivenProto) {
67   return MediaKeySystemAccessBinding::Wrap(aCx, this, aGivenProto);
68 }
69 
GetParentObject() const70 nsPIDOMWindowInner* MediaKeySystemAccess::GetParentObject() const {
71   return mParent;
72 }
73 
GetKeySystem(nsString & aOutKeySystem) const74 void MediaKeySystemAccess::GetKeySystem(nsString& aOutKeySystem) const {
75   aOutKeySystem.Assign(mKeySystem);
76 }
77 
GetConfiguration(MediaKeySystemConfiguration & aConfig)78 void MediaKeySystemAccess::GetConfiguration(
79     MediaKeySystemConfiguration& aConfig) {
80   aConfig = mConfig;
81 }
82 
CreateMediaKeys(ErrorResult & aRv)83 already_AddRefed<Promise> MediaKeySystemAccess::CreateMediaKeys(
84     ErrorResult& aRv) {
85   RefPtr<MediaKeys> keys(new MediaKeys(mParent, mKeySystem, mConfig));
86   return keys->Init(aRv);
87 }
88 
HavePluginForKeySystem(const nsCString & aKeySystem)89 static bool HavePluginForKeySystem(const nsCString& aKeySystem) {
90   nsCString api = NS_LITERAL_CSTRING(CHROMIUM_CDM_API);
91 
92   bool havePlugin = HaveGMPFor(api, {aKeySystem});
93 #ifdef MOZ_WIDGET_ANDROID
94   // Check if we can use MediaDrm for this keysystem.
95   if (!havePlugin) {
96     havePlugin = mozilla::java::MediaDrmProxy::IsSchemeSupported(aKeySystem);
97   }
98 #endif
99   return havePlugin;
100 }
101 
EnsureCDMInstalled(const nsAString & aKeySystem,nsACString & aOutMessage)102 static MediaKeySystemStatus EnsureCDMInstalled(const nsAString& aKeySystem,
103                                                nsACString& aOutMessage) {
104   if (!HavePluginForKeySystem(NS_ConvertUTF16toUTF8(aKeySystem))) {
105     aOutMessage = NS_LITERAL_CSTRING("CDM is not installed");
106     return MediaKeySystemStatus::Cdm_not_installed;
107   }
108 
109   return MediaKeySystemStatus::Available;
110 }
111 
112 /* static */
GetKeySystemStatus(const nsAString & aKeySystem,nsACString & aOutMessage)113 MediaKeySystemStatus MediaKeySystemAccess::GetKeySystemStatus(
114     const nsAString& aKeySystem, nsACString& aOutMessage) {
115   MOZ_ASSERT(MediaPrefs::EMEEnabled() || IsClearkeyKeySystem(aKeySystem));
116 
117   if (IsClearkeyKeySystem(aKeySystem)) {
118     return EnsureCDMInstalled(aKeySystem, aOutMessage);
119   }
120 
121   if (IsWidevineKeySystem(aKeySystem)) {
122     if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
123       if (!Preferences::GetBool("media.gmp-widevinecdm.enabled", false)) {
124         aOutMessage = NS_LITERAL_CSTRING("Widevine EME disabled");
125         return MediaKeySystemStatus::Cdm_disabled;
126       }
127       return EnsureCDMInstalled(aKeySystem, aOutMessage);
128 #ifdef MOZ_WIDGET_ANDROID
129     } else if (Preferences::GetBool("media.mediadrm-widevinecdm.visible",
130                                     false)) {
131       nsCString keySystem = NS_ConvertUTF16toUTF8(aKeySystem);
132       bool supported =
133           mozilla::java::MediaDrmProxy::IsSchemeSupported(keySystem);
134       if (!supported) {
135         aOutMessage = NS_LITERAL_CSTRING(
136             "KeySystem or Minimum API level not met for Widevine EME");
137         return MediaKeySystemStatus::Cdm_not_supported;
138       }
139       return MediaKeySystemStatus::Available;
140 #endif
141     }
142   }
143 
144   return MediaKeySystemStatus::Cdm_not_supported;
145 }
146 
147 typedef nsCString EMECodecString;
148 
149 static NS_NAMED_LITERAL_CSTRING(EME_CODEC_AAC, "aac");
150 static NS_NAMED_LITERAL_CSTRING(EME_CODEC_OPUS, "opus");
151 static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VORBIS, "vorbis");
152 static NS_NAMED_LITERAL_CSTRING(EME_CODEC_H264, "h264");
153 static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VP8, "vp8");
154 static NS_NAMED_LITERAL_CSTRING(EME_CODEC_VP9, "vp9");
155 
ToEMEAPICodecString(const nsString & aCodec)156 EMECodecString ToEMEAPICodecString(const nsString& aCodec) {
157   if (IsAACCodecString(aCodec)) {
158     return EME_CODEC_AAC;
159   }
160   if (aCodec.EqualsLiteral("opus")) {
161     return EME_CODEC_OPUS;
162   }
163   if (aCodec.EqualsLiteral("vorbis")) {
164     return EME_CODEC_VORBIS;
165   }
166   if (IsH264CodecString(aCodec)) {
167     return EME_CODEC_H264;
168   }
169   if (IsVP8CodecString(aCodec)) {
170     return EME_CODEC_VP8;
171   }
172   if (IsVP9CodecString(aCodec)) {
173     return EME_CODEC_VP9;
174   }
175   return EmptyCString();
176 }
177 
178 // A codec can be decrypted-and-decoded by the CDM, or only decrypted
179 // by the CDM and decoded by Gecko. Not both.
180 struct KeySystemContainerSupport {
IsSupportedmozilla::dom::KeySystemContainerSupport181   bool IsSupported() const {
182     return !mCodecsDecoded.IsEmpty() || !mCodecsDecrypted.IsEmpty();
183   }
184 
185   // CDM decrypts and decodes using a DRM robust decoder, and passes decoded
186   // samples back to Gecko for rendering.
DecryptsAndDecodesmozilla::dom::KeySystemContainerSupport187   bool DecryptsAndDecodes(EMECodecString aCodec) const {
188     return mCodecsDecoded.Contains(aCodec);
189   }
190 
191   // CDM decrypts and passes the decrypted samples back to Gecko for decoding.
Decryptsmozilla::dom::KeySystemContainerSupport192   bool Decrypts(EMECodecString aCodec) const {
193     return mCodecsDecrypted.Contains(aCodec);
194   }
195 
SetCanDecryptAndDecodemozilla::dom::KeySystemContainerSupport196   void SetCanDecryptAndDecode(EMECodecString aCodec) {
197     // Can't both decrypt and decrypt-and-decode a codec.
198     MOZ_ASSERT(!Decrypts(aCodec));
199     // Prevent duplicates.
200     MOZ_ASSERT(!DecryptsAndDecodes(aCodec));
201     mCodecsDecoded.AppendElement(aCodec);
202   }
203 
SetCanDecryptmozilla::dom::KeySystemContainerSupport204   void SetCanDecrypt(EMECodecString aCodec) {
205     // Prevent duplicates.
206     MOZ_ASSERT(!Decrypts(aCodec));
207     // Can't both decrypt and decrypt-and-decode a codec.
208     MOZ_ASSERT(!DecryptsAndDecodes(aCodec));
209     mCodecsDecrypted.AppendElement(aCodec);
210   }
211 
212  private:
213   nsTArray<EMECodecString> mCodecsDecoded;
214   nsTArray<EMECodecString> mCodecsDecrypted;
215 };
216 
217 enum class KeySystemFeatureSupport {
218   Prohibited = 1,
219   Requestable = 2,
220   Required = 3,
221 };
222 
223 struct KeySystemConfig {
224   nsString mKeySystem;
225   nsTArray<nsString> mInitDataTypes;
226   KeySystemFeatureSupport mPersistentState =
227       KeySystemFeatureSupport::Prohibited;
228   KeySystemFeatureSupport mDistinctiveIdentifier =
229       KeySystemFeatureSupport::Prohibited;
230   nsTArray<MediaKeySessionType> mSessionTypes;
231   nsTArray<nsString> mVideoRobustness;
232   nsTArray<nsString> mAudioRobustness;
233   KeySystemContainerSupport mMP4;
234   KeySystemContainerSupport mWebM;
235 };
236 
GetSupportedKeySystems()237 static nsTArray<KeySystemConfig> GetSupportedKeySystems() {
238   nsTArray<KeySystemConfig> keySystemConfigs;
239 
240   {
241     if (HavePluginForKeySystem(kEMEKeySystemClearkey)) {
242       KeySystemConfig clearkey;
243       clearkey.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemClearkey);
244       clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
245       clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
246       clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
247       clearkey.mPersistentState = KeySystemFeatureSupport::Requestable;
248       clearkey.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
249       clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
250       if (MediaPrefs::ClearKeyPersistentLicenseEnabled()) {
251         clearkey.mSessionTypes.AppendElement(
252             MediaKeySessionType::Persistent_license);
253       }
254 #if defined(XP_WIN)
255       // Clearkey CDM uses WMF's H.264 decoder on Windows.
256       if (WMFDecoderModule::HasH264()) {
257         clearkey.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
258       } else {
259         clearkey.mMP4.SetCanDecrypt(EME_CODEC_H264);
260       }
261 #else
262       clearkey.mMP4.SetCanDecrypt(EME_CODEC_H264);
263 #endif
264       clearkey.mMP4.SetCanDecrypt(EME_CODEC_AAC);
265       if (Preferences::GetBool("media.eme.vp9-in-mp4.enabled", false)) {
266         clearkey.mMP4.SetCanDecrypt(EME_CODEC_VP9);
267       }
268       clearkey.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
269       clearkey.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
270       clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP8);
271       clearkey.mWebM.SetCanDecrypt(EME_CODEC_VP9);
272       keySystemConfigs.AppendElement(Move(clearkey));
273     }
274   }
275   {
276     if (HavePluginForKeySystem(kEMEKeySystemWidevine)) {
277       KeySystemConfig widevine;
278       widevine.mKeySystem = NS_ConvertUTF8toUTF16(kEMEKeySystemWidevine);
279       widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
280       widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
281       widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
282       widevine.mPersistentState = KeySystemFeatureSupport::Requestable;
283       widevine.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
284       widevine.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
285 #ifdef MOZ_WIDGET_ANDROID
286       widevine.mSessionTypes.AppendElement(
287           MediaKeySessionType::Persistent_license);
288 #endif
289       widevine.mAudioRobustness.AppendElement(
290           NS_LITERAL_STRING("SW_SECURE_CRYPTO"));
291       widevine.mVideoRobustness.AppendElement(
292           NS_LITERAL_STRING("SW_SECURE_CRYPTO"));
293       widevine.mVideoRobustness.AppendElement(
294           NS_LITERAL_STRING("SW_SECURE_DECODE"));
295 #if defined(XP_WIN)
296       // Widevine CDM doesn't include an AAC decoder. So if WMF can't
297       // decode AAC, and a codec wasn't specified, be conservative
298       // and reject the MediaKeys request, since we assume Widevine
299       // will be used with AAC.
300       if (WMFDecoderModule::HasAAC()) {
301         widevine.mMP4.SetCanDecrypt(EME_CODEC_AAC);
302       }
303 #elif !defined(MOZ_WIDGET_ANDROID)
304       widevine.mMP4.SetCanDecrypt(EME_CODEC_AAC);
305 #endif
306 
307 #if defined(MOZ_WIDGET_ANDROID)
308       using namespace mozilla::java;
309       // MediaDrm.isCryptoSchemeSupported only allows passing
310       // "video/mp4" or "video/webm" for mimetype string.
311       // See
312       // https://developer.android.com/reference/android/media/MediaDrm.html#isCryptoSchemeSupported(java.util.UUID,
313       // java.lang.String) for more detail.
314       typedef struct {
315         const nsCString& mMimeType;
316         const nsCString& mEMECodecType;
317         const char16_t* mCodecType;
318         KeySystemContainerSupport* mSupportType;
319       } DataForValidation;
320 
321       DataForValidation validationList[] = {
322           {nsCString(VIDEO_MP4), EME_CODEC_H264, MediaDrmProxy::AVC,
323            &widevine.mMP4},
324           {nsCString(VIDEO_MP4), EME_CODEC_VP9, MediaDrmProxy::AVC,
325            &widevine.mMP4},
326           {nsCString(AUDIO_MP4), EME_CODEC_AAC, MediaDrmProxy::AAC,
327            &widevine.mMP4},
328           {nsCString(VIDEO_WEBM), EME_CODEC_VP8, MediaDrmProxy::VP8,
329            &widevine.mWebM},
330           {nsCString(VIDEO_WEBM), EME_CODEC_VP9, MediaDrmProxy::VP9,
331            &widevine.mWebM},
332           {nsCString(AUDIO_WEBM), EME_CODEC_VORBIS, MediaDrmProxy::VORBIS,
333            &widevine.mWebM},
334           {nsCString(AUDIO_WEBM), EME_CODEC_OPUS, MediaDrmProxy::OPUS,
335            &widevine.mWebM},
336       };
337 
338       for (const auto& data : validationList) {
339         if (MediaDrmProxy::IsCryptoSchemeSupported(kEMEKeySystemWidevine,
340                                                    data.mMimeType)) {
341           if (MediaDrmProxy::CanDecode(data.mCodecType)) {
342             data.mSupportType->SetCanDecryptAndDecode(data.mEMECodecType);
343           } else {
344             data.mSupportType->SetCanDecrypt(data.mEMECodecType);
345           }
346         }
347       }
348 #else
349       widevine.mMP4.SetCanDecryptAndDecode(EME_CODEC_H264);
350       if (Preferences::GetBool("media.eme.vp9-in-mp4.enabled", false)) {
351         widevine.mMP4.SetCanDecryptAndDecode(EME_CODEC_VP9);
352       }
353       widevine.mWebM.SetCanDecrypt(EME_CODEC_VORBIS);
354       widevine.mWebM.SetCanDecrypt(EME_CODEC_OPUS);
355       widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP8);
356       widevine.mWebM.SetCanDecryptAndDecode(EME_CODEC_VP9);
357 #endif
358       keySystemConfigs.AppendElement(Move(widevine));
359     }
360   }
361 
362   return keySystemConfigs;
363 }
364 
GetKeySystemConfig(const nsAString & aKeySystem,KeySystemConfig & aOutKeySystemConfig)365 static bool GetKeySystemConfig(const nsAString& aKeySystem,
366                                KeySystemConfig& aOutKeySystemConfig) {
367   for (auto&& config : GetSupportedKeySystems()) {
368     if (config.mKeySystem.Equals(aKeySystem)) {
369       aOutKeySystemConfig = mozilla::Move(config);
370       return true;
371     }
372   }
373   // No matching key system found.
374   return false;
375 }
376 
377 /* static */
KeySystemSupportsInitDataType(const nsAString & aKeySystem,const nsAString & aInitDataType)378 bool MediaKeySystemAccess::KeySystemSupportsInitDataType(
379     const nsAString& aKeySystem, const nsAString& aInitDataType) {
380   KeySystemConfig implementation;
381   return GetKeySystemConfig(aKeySystem, implementation) &&
382          implementation.mInitDataTypes.Contains(aInitDataType);
383 }
384 
385 enum CodecType { Audio, Video, Invalid };
386 
CanDecryptAndDecode(const nsString & aKeySystem,const nsString & aContentType,CodecType aCodecType,const KeySystemContainerSupport & aContainerSupport,const nsTArray<EMECodecString> & aCodecs,DecoderDoctorDiagnostics * aDiagnostics)387 static bool CanDecryptAndDecode(
388     const nsString& aKeySystem, const nsString& aContentType,
389     CodecType aCodecType, const KeySystemContainerSupport& aContainerSupport,
390     const nsTArray<EMECodecString>& aCodecs,
391     DecoderDoctorDiagnostics* aDiagnostics) {
392   MOZ_ASSERT(aCodecType != Invalid);
393   for (const EMECodecString& codec : aCodecs) {
394     MOZ_ASSERT(!codec.IsEmpty());
395 
396     if (aContainerSupport.DecryptsAndDecodes(codec)) {
397       // GMP can decrypt-and-decode this codec.
398       continue;
399     }
400 
401     if (aContainerSupport.Decrypts(codec) &&
402         NS_SUCCEEDED(
403             MediaSource::IsTypeSupported(aContentType, aDiagnostics))) {
404       // GMP can decrypt and is allowed to return compressed samples to
405       // Gecko to decode, and Gecko has a decoder.
406       continue;
407     }
408 
409       // Neither the GMP nor Gecko can both decrypt and decode. We don't
410       // support this codec.
411 
412 #if defined(XP_WIN)
413     // Widevine CDM doesn't include an AAC decoder. So if WMF can't
414     // decode AAC, and a codec wasn't specified, be conservative
415     // and reject the MediaKeys request, since we assume Widevine
416     // will be used with AAC.
417     if (codec == EME_CODEC_AAC && IsWidevineKeySystem(aKeySystem) &&
418         !WMFDecoderModule::HasAAC()) {
419       if (aDiagnostics) {
420         aDiagnostics->SetKeySystemIssue(
421             DecoderDoctorDiagnostics::eWidevineWithNoWMF);
422       }
423     }
424 #endif
425     return false;
426   }
427   return true;
428 }
429 
ToSessionType(const nsAString & aSessionType,MediaKeySessionType & aOutType)430 static bool ToSessionType(const nsAString& aSessionType,
431                           MediaKeySessionType& aOutType) {
432   if (aSessionType.Equals(ToString(MediaKeySessionType::Temporary))) {
433     aOutType = MediaKeySessionType::Temporary;
434     return true;
435   }
436   if (aSessionType.Equals(ToString(MediaKeySessionType::Persistent_license))) {
437     aOutType = MediaKeySessionType::Persistent_license;
438     return true;
439   }
440   return false;
441 }
442 
443 // 5.2.1 Is persistent session type?
IsPersistentSessionType(MediaKeySessionType aSessionType)444 static bool IsPersistentSessionType(MediaKeySessionType aSessionType) {
445   return aSessionType == MediaKeySessionType::Persistent_license;
446 }
447 
GetMajorType(const MediaMIMEType & aMIMEType)448 CodecType GetMajorType(const MediaMIMEType& aMIMEType) {
449   if (aMIMEType.HasAudioMajorType()) {
450     return Audio;
451   }
452   if (aMIMEType.HasVideoMajorType()) {
453     return Video;
454   }
455   return Invalid;
456 }
457 
GetCodecType(const EMECodecString & aCodec)458 static CodecType GetCodecType(const EMECodecString& aCodec) {
459   if (aCodec.Equals(EME_CODEC_AAC) || aCodec.Equals(EME_CODEC_OPUS) ||
460       aCodec.Equals(EME_CODEC_VORBIS)) {
461     return Audio;
462   }
463   if (aCodec.Equals(EME_CODEC_H264) || aCodec.Equals(EME_CODEC_VP8) ||
464       aCodec.Equals(EME_CODEC_VP9)) {
465     return Video;
466   }
467   return Invalid;
468 }
469 
AllCodecsOfType(const nsTArray<EMECodecString> & aCodecs,const CodecType aCodecType)470 static bool AllCodecsOfType(const nsTArray<EMECodecString>& aCodecs,
471                             const CodecType aCodecType) {
472   for (const EMECodecString& codec : aCodecs) {
473     if (GetCodecType(codec) != aCodecType) {
474       return false;
475     }
476   }
477   return true;
478 }
479 
IsParameterUnrecognized(const nsAString & aContentType)480 static bool IsParameterUnrecognized(const nsAString& aContentType) {
481   nsAutoString contentType(aContentType);
482   contentType.StripWhitespace();
483 
484   nsTArray<nsString> params;
485   nsAString::const_iterator start, end, semicolon, equalSign;
486   contentType.BeginReading(start);
487   contentType.EndReading(end);
488   semicolon = start;
489   // Find any substring between ';' & '='.
490   while (semicolon != end) {
491     if (FindCharInReadable(';', semicolon, end)) {
492       equalSign = ++semicolon;
493       if (FindCharInReadable('=', equalSign, end)) {
494         params.AppendElement(Substring(semicolon, equalSign));
495         semicolon = equalSign;
496       }
497     }
498   }
499 
500   for (auto param : params) {
501     if (!param.LowerCaseEqualsLiteral("codecs") &&
502         !param.LowerCaseEqualsLiteral("profiles")) {
503       return true;
504     }
505   }
506   return false;
507 }
508 
509 // 3.1.2.3 Get Supported Capabilities for Audio/Video Type
GetSupportedCapabilities(const CodecType aCodecType,const nsTArray<MediaKeySystemMediaCapability> & aRequestedCapabilities,const MediaKeySystemConfiguration & aPartialConfig,const KeySystemConfig & aKeySystem,DecoderDoctorDiagnostics * aDiagnostics,const std::function<void (const char *)> & aDeprecationLogFn)510 static Sequence<MediaKeySystemMediaCapability> GetSupportedCapabilities(
511     const CodecType aCodecType,
512     const nsTArray<MediaKeySystemMediaCapability>& aRequestedCapabilities,
513     const MediaKeySystemConfiguration& aPartialConfig,
514     const KeySystemConfig& aKeySystem, DecoderDoctorDiagnostics* aDiagnostics,
515     const std::function<void(const char*)>& aDeprecationLogFn) {
516   // Let local accumulated configuration be a local copy of partial
517   // configuration. (Note: It's not necessary for us to maintain a local copy,
518   // as we don't need to test whether capabilites from previous calls to this
519   // algorithm work with the capabilities currently being considered in this
520   // call. )
521 
522   // Let supported media capabilities be an empty sequence of
523   // MediaKeySystemMediaCapability dictionaries.
524   Sequence<MediaKeySystemMediaCapability> supportedCapabilities;
525 
526   // For each requested media capability in requested media capabilities:
527   for (const MediaKeySystemMediaCapability& capabilities :
528        aRequestedCapabilities) {
529     // Let content type be requested media capability's contentType member.
530     const nsString& contentTypeString = capabilities.mContentType;
531     // Let robustness be requested media capability's robustness member.
532     const nsString& robustness = capabilities.mRobustness;
533     // If content type is the empty string, return null.
534     if (contentTypeString.IsEmpty()) {
535       EME_LOG(
536           "MediaKeySystemConfiguration (label='%s') "
537           "MediaKeySystemMediaCapability('%s','%s') rejected; "
538           "audio or video capability has empty contentType.",
539           NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
540           NS_ConvertUTF16toUTF8(contentTypeString).get(),
541           NS_ConvertUTF16toUTF8(robustness).get());
542       return Sequence<MediaKeySystemMediaCapability>();
543     }
544     // If content type is an invalid or unrecognized MIME type, continue
545     // to the next iteration.
546     Maybe<MediaContainerType> maybeContainerType =
547         MakeMediaContainerType(contentTypeString);
548     if (!maybeContainerType) {
549       EME_LOG(
550           "MediaKeySystemConfiguration (label='%s') "
551           "MediaKeySystemMediaCapability('%s','%s') unsupported; "
552           "failed to parse contentTypeString as MIME type.",
553           NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
554           NS_ConvertUTF16toUTF8(contentTypeString).get(),
555           NS_ConvertUTF16toUTF8(robustness).get());
556       continue;
557     }
558     const MediaContainerType& containerType = *maybeContainerType;
559     bool invalid = false;
560     nsTArray<EMECodecString> codecs;
561     for (const auto& codecString :
562          containerType.ExtendedType().Codecs().Range()) {
563       EMECodecString emeCodec = ToEMEAPICodecString(nsString(codecString));
564       if (emeCodec.IsEmpty()) {
565         invalid = true;
566         EME_LOG(
567             "MediaKeySystemConfiguration (label='%s') "
568             "MediaKeySystemMediaCapability('%s','%s') unsupported; "
569             "'%s' is an invalid codec string.",
570             NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
571             NS_ConvertUTF16toUTF8(contentTypeString).get(),
572             NS_ConvertUTF16toUTF8(robustness).get(),
573             NS_ConvertUTF16toUTF8(codecString).get());
574         break;
575       }
576       codecs.AppendElement(emeCodec);
577     }
578     if (invalid) {
579       continue;
580     }
581 
582     // If the user agent does not support container, continue to the next
583     // iteration. The case-sensitivity of string comparisons is determined by
584     // the appropriate RFC. (Note: Per RFC 6838 [RFC6838], "Both top-level type
585     // and subtype names are case-insensitive."'. We're using
586     // nsContentTypeParser and that is case-insensitive and converts all its
587     // parameter outputs to lower case.)
588     const bool isMP4 =
589         DecoderTraits::IsMP4SupportedType(containerType, aDiagnostics);
590     if (isMP4 && !aKeySystem.mMP4.IsSupported()) {
591       EME_LOG(
592           "MediaKeySystemConfiguration (label='%s') "
593           "MediaKeySystemMediaCapability('%s','%s') unsupported; "
594           "MP4 requested but unsupported.",
595           NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
596           NS_ConvertUTF16toUTF8(contentTypeString).get(),
597           NS_ConvertUTF16toUTF8(robustness).get());
598       continue;
599     }
600     const bool isWebM = WebMDecoder::IsSupportedType(containerType);
601     if (isWebM && !aKeySystem.mWebM.IsSupported()) {
602       EME_LOG(
603           "MediaKeySystemConfiguration (label='%s') "
604           "MediaKeySystemMediaCapability('%s','%s') unsupported; "
605           "WebM requested but unsupported.",
606           NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
607           NS_ConvertUTF16toUTF8(contentTypeString).get(),
608           NS_ConvertUTF16toUTF8(robustness).get());
609       continue;
610     }
611     if (!isMP4 && !isWebM) {
612       EME_LOG(
613           "MediaKeySystemConfiguration (label='%s') "
614           "MediaKeySystemMediaCapability('%s','%s') unsupported; "
615           "Unsupported or unrecognized container requested.",
616           NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
617           NS_ConvertUTF16toUTF8(contentTypeString).get(),
618           NS_ConvertUTF16toUTF8(robustness).get());
619       continue;
620     }
621 
622     // Let parameters be the RFC 6381[RFC6381] parameters, if any, specified by
623     // content type.
624     // If the user agent does not recognize one or more parameters, continue to
625     // the next iteration.
626     if (IsParameterUnrecognized(contentTypeString)) {
627       continue;
628     }
629 
630     // Let media types be the set of codecs and codec constraints specified by
631     // parameters. The case-sensitivity of string comparisons is determined by
632     // the appropriate RFC or other specification.
633     // (Note: codecs array is 'parameter').
634 
635     // If media types is empty:
636     if (codecs.IsEmpty()) {
637       // Log deprecation warning to encourage authors to not do this!
638       aDeprecationLogFn("MediaEMENoCodecsDeprecatedWarning");
639       // TODO: Remove this once we're sure it doesn't break the web.
640       // If container normatively implies a specific set of codecs and codec
641       // constraints: Let parameters be that set.
642       if (isMP4) {
643         if (aCodecType == Audio) {
644           codecs.AppendElement(EME_CODEC_AAC);
645         } else if (aCodecType == Video) {
646           codecs.AppendElement(EME_CODEC_H264);
647         }
648       } else if (isWebM) {
649         if (aCodecType == Audio) {
650           codecs.AppendElement(EME_CODEC_VORBIS);
651         } else if (aCodecType == Video) {
652           codecs.AppendElement(EME_CODEC_VP8);
653         }
654       }
655       // Otherwise: Continue to the next iteration.
656       // (Note: all containers we support have implied codecs, so don't continue
657       // here.)
658     }
659 
660     // If container type is not strictly a audio/video type, continue to the
661     // next iteration.
662     const auto majorType = GetMajorType(containerType.Type());
663     if (majorType == Invalid) {
664       EME_LOG(
665           "MediaKeySystemConfiguration (label='%s') "
666           "MediaKeySystemMediaCapability('%s','%s') unsupported; "
667           "MIME type is not an audio or video MIME type.",
668           NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
669           NS_ConvertUTF16toUTF8(contentTypeString).get(),
670           NS_ConvertUTF16toUTF8(robustness).get());
671       continue;
672     }
673     if (majorType != aCodecType || !AllCodecsOfType(codecs, aCodecType)) {
674       EME_LOG(
675           "MediaKeySystemConfiguration (label='%s') "
676           "MediaKeySystemMediaCapability('%s','%s') unsupported; "
677           "MIME type mixes audio codecs in video capabilities "
678           "or video codecs in audio capabilities.",
679           NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
680           NS_ConvertUTF16toUTF8(contentTypeString).get(),
681           NS_ConvertUTF16toUTF8(robustness).get());
682       continue;
683     }
684     // If robustness is not the empty string and contains an unrecognized
685     // value or a value not supported by implementation, continue to the
686     // next iteration. String comparison is case-sensitive.
687     if (!robustness.IsEmpty()) {
688       if (majorType == Audio &&
689           !aKeySystem.mAudioRobustness.Contains(robustness)) {
690         EME_LOG(
691             "MediaKeySystemConfiguration (label='%s') "
692             "MediaKeySystemMediaCapability('%s','%s') unsupported; "
693             "unsupported robustness string.",
694             NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
695             NS_ConvertUTF16toUTF8(contentTypeString).get(),
696             NS_ConvertUTF16toUTF8(robustness).get());
697         continue;
698       }
699       if (majorType == Video &&
700           !aKeySystem.mVideoRobustness.Contains(robustness)) {
701         EME_LOG(
702             "MediaKeySystemConfiguration (label='%s') "
703             "MediaKeySystemMediaCapability('%s','%s') unsupported; "
704             "unsupported robustness string.",
705             NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
706             NS_ConvertUTF16toUTF8(contentTypeString).get(),
707             NS_ConvertUTF16toUTF8(robustness).get());
708         continue;
709       }
710       // Note: specified robustness requirements are satisfied.
711     }
712 
713     // If the user agent and implementation definitely support playback of
714     // encrypted media data for the combination of container, media types,
715     // robustness and local accumulated configuration in combination with
716     // restrictions...
717     const auto& containerSupport = isMP4 ? aKeySystem.mMP4 : aKeySystem.mWebM;
718     if (!CanDecryptAndDecode(aKeySystem.mKeySystem, contentTypeString,
719                              majorType, containerSupport, codecs,
720                              aDiagnostics)) {
721       EME_LOG(
722           "MediaKeySystemConfiguration (label='%s') "
723           "MediaKeySystemMediaCapability('%s','%s') unsupported; "
724           "codec unsupported by CDM requested.",
725           NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
726           NS_ConvertUTF16toUTF8(contentTypeString).get(),
727           NS_ConvertUTF16toUTF8(robustness).get());
728       continue;
729     }
730 
731     // ... add requested media capability to supported media capabilities.
732     if (!supportedCapabilities.AppendElement(capabilities, mozilla::fallible)) {
733       NS_WARNING("GetSupportedCapabilities: Malloc failure");
734       return Sequence<MediaKeySystemMediaCapability>();
735     }
736 
737     // Note: omitting steps 3.13.2, our robustness is not sophisticated enough
738     // to require considering all requirements together.
739   }
740   return Move(supportedCapabilities);
741 }
742 
743 // "Get Supported Configuration and Consent" algorithm, steps 4-7 for
744 // distinctive identifier, and steps 8-11 for persistent state. The steps
745 // are the same for both requirements/features, so we factor them out into
746 // a single function.
CheckRequirement(const MediaKeysRequirement aRequirement,const KeySystemFeatureSupport aFeatureSupport,MediaKeysRequirement & aOutRequirement)747 static bool CheckRequirement(const MediaKeysRequirement aRequirement,
748                              const KeySystemFeatureSupport aFeatureSupport,
749                              MediaKeysRequirement& aOutRequirement) {
750   // Let requirement be the value of candidate configuration's member.
751   MediaKeysRequirement requirement = aRequirement;
752   // If requirement is "optional" and feature is not allowed according to
753   // restrictions, set requirement to "not-allowed".
754   if (aRequirement == MediaKeysRequirement::Optional &&
755       aFeatureSupport == KeySystemFeatureSupport::Prohibited) {
756     requirement = MediaKeysRequirement::Not_allowed;
757   }
758 
759   // Follow the steps for requirement from the following list:
760   switch (requirement) {
761     case MediaKeysRequirement::Required: {
762       // If the implementation does not support use of requirement in
763       // combination with accumulated configuration and restrictions, return
764       // NotSupported.
765       if (aFeatureSupport == KeySystemFeatureSupport::Prohibited) {
766         return false;
767       }
768       break;
769     }
770     case MediaKeysRequirement::Optional: {
771       // Continue with the following steps.
772       break;
773     }
774     case MediaKeysRequirement::Not_allowed: {
775       // If the implementation requires use of feature in combination with
776       // accumulated configuration and restrictions, return NotSupported.
777       if (aFeatureSupport == KeySystemFeatureSupport::Required) {
778         return false;
779       }
780       break;
781     }
782     default: { return false; }
783   }
784 
785   // Set the requirement member of accumulated configuration to equal
786   // calculated requirement.
787   aOutRequirement = requirement;
788 
789   return true;
790 }
791 
792 // 3.1.2.2, step 12
793 // Follow the steps for the first matching condition from the following list:
794 // If the sessionTypes member is present in candidate configuration.
795 // Let session types be candidate configuration's sessionTypes member.
796 // Otherwise let session types be ["temporary"].
797 // Note: This returns an empty array on malloc failure.
UnboxSessionTypes(const Optional<Sequence<nsString>> & aSessionTypes)798 static Sequence<nsString> UnboxSessionTypes(
799     const Optional<Sequence<nsString>>& aSessionTypes) {
800   Sequence<nsString> sessionTypes;
801   if (aSessionTypes.WasPassed()) {
802     sessionTypes = aSessionTypes.Value();
803   } else {
804     // Note: fallible. Results in an empty array.
805     sessionTypes.AppendElement(ToString(MediaKeySessionType::Temporary),
806                                mozilla::fallible);
807   }
808   return sessionTypes;
809 }
810 
811 // 3.1.2.2 Get Supported Configuration and Consent
GetSupportedConfig(const KeySystemConfig & aKeySystem,const MediaKeySystemConfiguration & aCandidate,MediaKeySystemConfiguration & aOutConfig,DecoderDoctorDiagnostics * aDiagnostics,bool aInPrivateBrowsing,const std::function<void (const char *)> & aDeprecationLogFn)812 static bool GetSupportedConfig(
813     const KeySystemConfig& aKeySystem,
814     const MediaKeySystemConfiguration& aCandidate,
815     MediaKeySystemConfiguration& aOutConfig,
816     DecoderDoctorDiagnostics* aDiagnostics, bool aInPrivateBrowsing,
817     const std::function<void(const char*)>& aDeprecationLogFn) {
818   // Let accumulated configuration be a new MediaKeySystemConfiguration
819   // dictionary.
820   MediaKeySystemConfiguration config;
821   // Set the label member of accumulated configuration to equal the label member
822   // of candidate configuration.
823   config.mLabel = aCandidate.mLabel;
824   // If the initDataTypes member of candidate configuration is non-empty, run
825   // the following steps:
826   if (!aCandidate.mInitDataTypes.IsEmpty()) {
827     // Let supported types be an empty sequence of DOMStrings.
828     nsTArray<nsString> supportedTypes;
829     // For each value in candidate configuration's initDataTypes member:
830     for (const nsString& initDataType : aCandidate.mInitDataTypes) {
831       // Let initDataType be the value.
832       // If the implementation supports generating requests based on
833       // initDataType, add initDataType to supported types. String comparison is
834       // case-sensitive. The empty string is never supported.
835       if (aKeySystem.mInitDataTypes.Contains(initDataType)) {
836         supportedTypes.AppendElement(initDataType);
837       }
838     }
839     // If supported types is empty, return NotSupported.
840     if (supportedTypes.IsEmpty()) {
841       EME_LOG(
842           "MediaKeySystemConfiguration (label='%s') rejected; "
843           "no supported initDataTypes provided.",
844           NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
845       return false;
846     }
847     // Set the initDataTypes member of accumulated configuration to supported
848     // types.
849     if (!config.mInitDataTypes.Assign(supportedTypes)) {
850       return false;
851     }
852   }
853 
854   if (!CheckRequirement(aCandidate.mDistinctiveIdentifier,
855                         aKeySystem.mDistinctiveIdentifier,
856                         config.mDistinctiveIdentifier)) {
857     EME_LOG(
858         "MediaKeySystemConfiguration (label='%s') rejected; "
859         "distinctiveIdentifier requirement not satisfied.",
860         NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
861     return false;
862   }
863 
864   if (!CheckRequirement(aCandidate.mPersistentState,
865                         aKeySystem.mPersistentState, config.mPersistentState)) {
866     EME_LOG(
867         "MediaKeySystemConfiguration (label='%s') rejected; "
868         "persistentState requirement not satisfied.",
869         NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
870     return false;
871   }
872 
873   if (config.mPersistentState == MediaKeysRequirement::Required &&
874       aInPrivateBrowsing) {
875     EME_LOG(
876         "MediaKeySystemConfiguration (label='%s') rejected; "
877         "persistentState requested in Private Browsing window.",
878         NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
879     return false;
880   }
881 
882   Sequence<nsString> sessionTypes(UnboxSessionTypes(aCandidate.mSessionTypes));
883   if (sessionTypes.IsEmpty()) {
884     // Malloc failure.
885     return false;
886   }
887 
888   // For each value in session types:
889   for (const auto& sessionTypeString : sessionTypes) {
890     // Let session type be the value.
891     MediaKeySessionType sessionType;
892     if (!ToSessionType(sessionTypeString, sessionType)) {
893       // (Assume invalid sessionType is unsupported as per steps below).
894       EME_LOG(
895           "MediaKeySystemConfiguration (label='%s') rejected; "
896           "invalid session type specified.",
897           NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
898       return false;
899     }
900     // If accumulated configuration's persistentState value is "not-allowed"
901     // and the Is persistent session type? algorithm returns true for session
902     // type return NotSupported.
903     if (config.mPersistentState == MediaKeysRequirement::Not_allowed &&
904         IsPersistentSessionType(sessionType)) {
905       EME_LOG(
906           "MediaKeySystemConfiguration (label='%s') rejected; "
907           "persistent session requested but keysystem doesn't"
908           "support persistent state.",
909           NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
910       return false;
911     }
912     // If the implementation does not support session type in combination
913     // with accumulated configuration and restrictions for other reasons,
914     // return NotSupported.
915     if (!aKeySystem.mSessionTypes.Contains(sessionType)) {
916       EME_LOG(
917           "MediaKeySystemConfiguration (label='%s') rejected; "
918           "session type '%s' unsupported by keySystem.",
919           NS_ConvertUTF16toUTF8(aCandidate.mLabel).get(),
920           NS_ConvertUTF16toUTF8(sessionTypeString).get());
921       return false;
922     }
923     // If accumulated configuration's persistentState value is "optional"
924     // and the result of running the Is persistent session type? algorithm
925     // on session type is true, change accumulated configuration's
926     // persistentState value to "required".
927     if (config.mPersistentState == MediaKeysRequirement::Optional &&
928         IsPersistentSessionType(sessionType)) {
929       config.mPersistentState = MediaKeysRequirement::Required;
930     }
931   }
932   // Set the sessionTypes member of accumulated configuration to session types.
933   config.mSessionTypes.Construct(Move(sessionTypes));
934 
935   // If the videoCapabilities and audioCapabilities members in candidate
936   // configuration are both empty, return NotSupported.
937   if (aCandidate.mAudioCapabilities.IsEmpty() &&
938       aCandidate.mVideoCapabilities.IsEmpty()) {
939     // TODO: Most sites using EME still don't pass capabilities, so we
940     // can't reject on it yet without breaking them. So add this later.
941     // Log deprecation warning to encourage authors to not do this!
942     aDeprecationLogFn("MediaEMENoCapabilitiesDeprecatedWarning");
943   }
944 
945   // If the videoCapabilities member in candidate configuration is non-empty:
946   if (!aCandidate.mVideoCapabilities.IsEmpty()) {
947     // Let video capabilities be the result of executing the Get Supported
948     // Capabilities for Audio/Video Type algorithm on Video, candidate
949     // configuration's videoCapabilities member, accumulated configuration,
950     // and restrictions.
951     Sequence<MediaKeySystemMediaCapability> caps =
952         GetSupportedCapabilities(Video, aCandidate.mVideoCapabilities, config,
953                                  aKeySystem, aDiagnostics, aDeprecationLogFn);
954     // If video capabilities is null, return NotSupported.
955     if (caps.IsEmpty()) {
956       EME_LOG(
957           "MediaKeySystemConfiguration (label='%s') rejected; "
958           "no supported video capabilities.",
959           NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
960       return false;
961     }
962     // Set the videoCapabilities member of accumulated configuration to video
963     // capabilities.
964     config.mVideoCapabilities = Move(caps);
965   } else {
966     // Otherwise:
967     // Set the videoCapabilities member of accumulated configuration to an empty
968     // sequence.
969   }
970 
971   // If the audioCapabilities member in candidate configuration is non-empty:
972   if (!aCandidate.mAudioCapabilities.IsEmpty()) {
973     // Let audio capabilities be the result of executing the Get Supported
974     // Capabilities for Audio/Video Type algorithm on Audio, candidate
975     // configuration's audioCapabilities member, accumulated configuration, and
976     // restrictions.
977     Sequence<MediaKeySystemMediaCapability> caps =
978         GetSupportedCapabilities(Audio, aCandidate.mAudioCapabilities, config,
979                                  aKeySystem, aDiagnostics, aDeprecationLogFn);
980     // If audio capabilities is null, return NotSupported.
981     if (caps.IsEmpty()) {
982       EME_LOG(
983           "MediaKeySystemConfiguration (label='%s') rejected; "
984           "no supported audio capabilities.",
985           NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
986       return false;
987     }
988     // Set the audioCapabilities member of accumulated configuration to audio
989     // capabilities.
990     config.mAudioCapabilities = Move(caps);
991   } else {
992     // Otherwise:
993     // Set the audioCapabilities member of accumulated configuration to an empty
994     // sequence.
995   }
996 
997   // If accumulated configuration's distinctiveIdentifier value is "optional",
998   // follow the steps for the first matching condition from the following list:
999   if (config.mDistinctiveIdentifier == MediaKeysRequirement::Optional) {
1000     // If the implementation requires use Distinctive Identifier(s) or
1001     // Distinctive Permanent Identifier(s) for any of the combinations
1002     // in accumulated configuration
1003     if (aKeySystem.mDistinctiveIdentifier ==
1004         KeySystemFeatureSupport::Required) {
1005       // Change accumulated configuration's distinctiveIdentifier value to
1006       // "required".
1007       config.mDistinctiveIdentifier = MediaKeysRequirement::Required;
1008     } else {
1009       // Otherwise, change accumulated configuration's distinctiveIdentifier
1010       // value to "not-allowed".
1011       config.mDistinctiveIdentifier = MediaKeysRequirement::Not_allowed;
1012     }
1013   }
1014 
1015   // If accumulated configuration's persistentState value is "optional", follow
1016   // the steps for the first matching condition from the following list:
1017   if (config.mPersistentState == MediaKeysRequirement::Optional) {
1018     // If the implementation requires persisting state for any of the
1019     // combinations in accumulated configuration
1020     if (aKeySystem.mPersistentState == KeySystemFeatureSupport::Required) {
1021       // Change accumulated configuration's persistentState value to "required".
1022       config.mPersistentState = MediaKeysRequirement::Required;
1023     } else {
1024       // Otherwise, change accumulated configuration's persistentState
1025       // value to "not-allowed".
1026       config.mPersistentState = MediaKeysRequirement::Not_allowed;
1027     }
1028   }
1029 
1030     // Note: Omitting steps 20-22. We don't ask for consent.
1031 
1032 #if defined(XP_WIN)
1033   // Widevine CDM doesn't include an AAC decoder. So if WMF can't decode AAC,
1034   // and a codec wasn't specified, be conservative and reject the MediaKeys
1035   // request.
1036   if (IsWidevineKeySystem(aKeySystem.mKeySystem) &&
1037       (aCandidate.mAudioCapabilities.IsEmpty() ||
1038        aCandidate.mVideoCapabilities.IsEmpty()) &&
1039       !WMFDecoderModule::HasAAC()) {
1040     if (aDiagnostics) {
1041       aDiagnostics->SetKeySystemIssue(
1042           DecoderDoctorDiagnostics::eWidevineWithNoWMF);
1043     }
1044     EME_LOG(
1045         "MediaKeySystemConfiguration (label='%s') rejected; "
1046         "WMF required for Widevine decoding, but it's not available.",
1047         NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
1048     return false;
1049   }
1050 #endif
1051 
1052   // Return accumulated configuration.
1053   aOutConfig = config;
1054 
1055   return true;
1056 }
1057 
1058 /* static */
GetSupportedConfig(const nsAString & aKeySystem,const Sequence<MediaKeySystemConfiguration> & aConfigs,MediaKeySystemConfiguration & aOutConfig,DecoderDoctorDiagnostics * aDiagnostics,bool aIsPrivateBrowsing,const std::function<void (const char *)> & aDeprecationLogFn)1059 bool MediaKeySystemAccess::GetSupportedConfig(
1060     const nsAString& aKeySystem,
1061     const Sequence<MediaKeySystemConfiguration>& aConfigs,
1062     MediaKeySystemConfiguration& aOutConfig,
1063     DecoderDoctorDiagnostics* aDiagnostics, bool aIsPrivateBrowsing,
1064     const std::function<void(const char*)>& aDeprecationLogFn) {
1065   KeySystemConfig implementation;
1066   if (!GetKeySystemConfig(aKeySystem, implementation)) {
1067     return false;
1068   }
1069   for (const MediaKeySystemConfiguration& candidate : aConfigs) {
1070     if (mozilla::dom::GetSupportedConfig(implementation, candidate, aOutConfig,
1071                                          aDiagnostics, aIsPrivateBrowsing,
1072                                          aDeprecationLogFn)) {
1073       return true;
1074     }
1075   }
1076 
1077   return false;
1078 }
1079 
1080 /* static */
NotifyObservers(nsPIDOMWindowInner * aWindow,const nsAString & aKeySystem,MediaKeySystemStatus aStatus)1081 void MediaKeySystemAccess::NotifyObservers(nsPIDOMWindowInner* aWindow,
1082                                            const nsAString& aKeySystem,
1083                                            MediaKeySystemStatus aStatus) {
1084   RequestMediaKeySystemAccessNotification data;
1085   data.mKeySystem = aKeySystem;
1086   data.mStatus = aStatus;
1087   nsAutoString json;
1088   data.ToJSON(json);
1089   EME_LOG("MediaKeySystemAccess::NotifyObservers() %s",
1090           NS_ConvertUTF16toUTF8(json).get());
1091   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
1092   if (obs) {
1093     obs->NotifyObservers(aWindow, "mediakeys-request", json.get());
1094   }
1095 }
1096 
ToCString(const nsString & aString)1097 static nsCString ToCString(const nsString& aString) {
1098   nsCString str("'");
1099   str.Append(NS_ConvertUTF16toUTF8(aString));
1100   str.AppendLiteral("'");
1101   return str;
1102 }
1103 
ToCString(const MediaKeysRequirement aValue)1104 static nsCString ToCString(const MediaKeysRequirement aValue) {
1105   nsCString str("'");
1106   str.Append(nsDependentCString(
1107       MediaKeysRequirementValues::strings[static_cast<uint32_t>(aValue)]
1108           .value));
1109   str.AppendLiteral("'");
1110   return str;
1111 }
1112 
ToCString(const MediaKeySystemMediaCapability & aValue)1113 static nsCString ToCString(const MediaKeySystemMediaCapability& aValue) {
1114   nsCString str;
1115   str.AppendLiteral("{contentType=");
1116   str.Append(ToCString(aValue.mContentType));
1117   str.AppendLiteral(", robustness=");
1118   str.Append(ToCString(aValue.mRobustness));
1119   str.AppendLiteral("}");
1120   return str;
1121 }
1122 
1123 template <class Type>
ToCString(const Sequence<Type> & aSequence)1124 static nsCString ToCString(const Sequence<Type>& aSequence) {
1125   nsCString str;
1126   str.AppendLiteral("[");
1127   for (size_t i = 0; i < aSequence.Length(); i++) {
1128     if (i != 0) {
1129       str.AppendLiteral(",");
1130     }
1131     str.Append(ToCString(aSequence[i]));
1132   }
1133   str.AppendLiteral("]");
1134   return str;
1135 }
1136 
1137 template <class Type>
ToCString(const Optional<Sequence<Type>> & aOptional)1138 static nsCString ToCString(const Optional<Sequence<Type>>& aOptional) {
1139   nsCString str;
1140   if (aOptional.WasPassed()) {
1141     str.Append(ToCString(aOptional.Value()));
1142   } else {
1143     str.AppendLiteral("[]");
1144   }
1145   return str;
1146 }
1147 
ToCString(const MediaKeySystemConfiguration & aConfig)1148 static nsCString ToCString(const MediaKeySystemConfiguration& aConfig) {
1149   nsCString str;
1150   str.AppendLiteral("{label=");
1151   str.Append(ToCString(aConfig.mLabel));
1152 
1153   str.AppendLiteral(", initDataTypes=");
1154   str.Append(ToCString(aConfig.mInitDataTypes));
1155 
1156   str.AppendLiteral(", audioCapabilities=");
1157   str.Append(ToCString(aConfig.mAudioCapabilities));
1158 
1159   str.AppendLiteral(", videoCapabilities=");
1160   str.Append(ToCString(aConfig.mVideoCapabilities));
1161 
1162   str.AppendLiteral(", distinctiveIdentifier=");
1163   str.Append(ToCString(aConfig.mDistinctiveIdentifier));
1164 
1165   str.AppendLiteral(", persistentState=");
1166   str.Append(ToCString(aConfig.mPersistentState));
1167 
1168   str.AppendLiteral(", sessionTypes=");
1169   str.Append(ToCString(aConfig.mSessionTypes));
1170 
1171   str.AppendLiteral("}");
1172 
1173   return str;
1174 }
1175 
1176 /* static */
ToCString(const Sequence<MediaKeySystemConfiguration> & aConfig)1177 nsCString MediaKeySystemAccess::ToCString(
1178     const Sequence<MediaKeySystemConfiguration>& aConfig) {
1179   return mozilla::dom::ToCString(aConfig);
1180 }
1181 
1182 }  // namespace dom
1183 }  // namespace mozilla
1184