1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "webencryptedmediaclient_impl.h"
8 #include "base/logging.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "media/base/key_systems.h"
13 #include "media/base/media_permission.h"
14 #include "media/blink/webcontentdecryptionmodule_impl.h"
15 #include "media/blink/webcontentdecryptionmoduleaccess_impl.h"
16 #include "net/base/mime_util.h"
17 #include "third_party/WebKit/public/platform/WebEncryptedMediaRequest.h"
18 #include "third_party/WebKit/public/platform/WebMediaKeySystemConfiguration.h"
19 #include "third_party/WebKit/public/platform/WebString.h"
20 #include "third_party/WebKit/public/platform/WebVector.h"
24 // These names are used by UMA.
25 const char kKeySystemSupportUMAPrefix
[] =
26 "Media.EME.RequestMediaKeySystemAccess.";
28 // TODO(jrummell): Convert to an enum. http://crbug.com/418239
29 const char kTemporarySessionType
[] = "temporary";
30 const char kPersistentLicenseSessionType
[] = "persistent-license";
31 const char kPersistentReleaseMessageSessionType
[] =
32 "persistent-release-message";
34 enum ConfigurationSupport
{
35 CONFIGURATION_NOT_SUPPORTED
,
36 CONFIGURATION_REQUIRES_PERMISSION
,
37 CONFIGURATION_SUPPORTED
,
40 static bool IsSupportedContentType(const std::string
& key_system
,
41 const std::string
& mime_type
,
42 const std::string
& codecs
) {
43 // TODO(sandersd): Move contentType parsing from Blink to here so that invalid
44 // parameters can be rejected. http://crbug.com/417561
45 // TODO(sandersd): Pass in the media type (audio or video) and check that the
46 // container type matches. http://crbug.com/457384
47 std::string container
= base::StringToLowerASCII(mime_type
);
49 // Check that |codecs| are supported by the CDM. This check does not handle
50 // extended codecs, so extended codec information is stripped.
51 // TODO(sandersd): Reject codecs that do not match the media type.
52 // http://crbug.com/457386
53 std::vector
<std::string
> codec_vector
;
54 net::ParseCodecString(codecs
, &codec_vector
, true);
55 if (!IsSupportedKeySystemWithMediaMimeType(container
, codec_vector
,
60 // Check that |codecs| are supported by Chrome. This is done primarily to
61 // validate extended codecs, but it also ensures that the CDM cannot support
62 // codecs that Chrome does not (which would be bad because it would require
63 // considering the accumulated configuration, and could affect whether secure
64 // decode is required).
65 // TODO(sandersd): Reject ambiguous codecs. http://crbug.com/374751
67 net::ParseCodecString(codecs
, &codec_vector
, false);
68 return net::AreSupportedMediaCodecs(codec_vector
);
71 static bool GetSupportedCapabilities(
72 const std::string
& key_system
,
73 const blink::WebVector
<blink::WebMediaKeySystemMediaCapability
>&
75 std::vector
<blink::WebMediaKeySystemMediaCapability
>*
76 media_type_capabilities
) {
78 // https://w3c.github.io/encrypted-media/#get-supported-capabilities-for-media-type
79 // 1. Let accumulated capabilities be partial configuration.
80 // (Skipped as there are no configuration-based codec restrictions.)
81 // 2. Let media type capabilities be empty.
82 DCHECK_EQ(media_type_capabilities
->size(), 0ul);
83 // 3. For each value in capabilities:
84 for (size_t i
= 0; i
< capabilities
.size(); i
++) {
85 // 3.1. Let contentType be the value's contentType member.
86 // 3.2. Let robustness be the value's robustness member.
87 const blink::WebMediaKeySystemMediaCapability
& capability
= capabilities
[i
];
88 // 3.3. If contentType is the empty string, return null.
89 if (capability
.mimeType
.isEmpty())
91 // 3.4-3.11. (Implemented by IsSupportedContentType().)
92 if (!base::IsStringASCII(capability
.mimeType
) ||
93 !base::IsStringASCII(capability
.codecs
) ||
94 !IsSupportedContentType(key_system
,
95 base::UTF16ToASCII(capability
.mimeType
),
96 base::UTF16ToASCII(capability
.codecs
))) {
99 // 3.12. If robustness is not the empty string, run the following steps:
100 // (Robustness is not supported.)
101 // TODO(sandersd): Implement robustness. http://crbug.com/442586
102 if (!capability
.robustness
.isEmpty()) {
103 LOG(WARNING
) << "Configuration rejected because rubustness strings are "
104 << "not yet supported.";
107 // 3.13. If the user agent and implementation do not support playback of
108 // encrypted media data as specified by configuration, including all
109 // media types, in combination with accumulated capabilities,
110 // continue to the next iteration.
111 // (Skipped as there are no configuration-based codec restrictions.)
112 // 3.14. Add configuration to media type capabilities.
113 media_type_capabilities
->push_back(capability
);
114 // 3.15. Add configuration to accumulated capabilities.
115 // (Skipped as there are no configuration-based codec restrictions.)
117 // 4. If media type capabilities is empty, return null.
118 // 5. Return media type capabilities.
119 return !media_type_capabilities
->empty();
122 static EmeFeatureRequirement
ConvertRequirement(
123 blink::WebMediaKeySystemConfiguration::Requirement requirement
) {
124 switch (requirement
) {
125 case blink::WebMediaKeySystemConfiguration::Requirement::Required
:
126 return EME_FEATURE_REQUIRED
;
127 case blink::WebMediaKeySystemConfiguration::Requirement::Optional
:
128 return EME_FEATURE_OPTIONAL
;
129 case blink::WebMediaKeySystemConfiguration::Requirement::NotAllowed
:
130 return EME_FEATURE_NOT_ALLOWED
;
134 return EME_FEATURE_NOT_ALLOWED
;
137 static ConfigurationSupport
GetSupportedConfiguration(
138 const std::string
& key_system
,
139 const blink::WebMediaKeySystemConfiguration
& candidate
,
140 blink::WebMediaKeySystemConfiguration
* accumulated_configuration
,
141 bool was_permission_requested
,
142 bool is_permission_granted
) {
143 DCHECK(was_permission_requested
|| !is_permission_granted
);
145 // It is possible to obtain user permission unless permission was already
146 // requested and denied.
147 bool is_permission_possible
=
148 !was_permission_requested
|| is_permission_granted
;
150 // From https://w3c.github.io/encrypted-media/#get-supported-configuration
151 // 1. Let accumulated configuration be empty. (Done by caller.)
152 // 2. If candidate configuration's initDataTypes attribute is not empty, run
153 // the following steps:
154 if (!candidate
.initDataTypes
.isEmpty()) {
155 // 2.1. Let supported types be empty.
156 std::vector
<blink::WebString
> supported_types
;
158 // 2.2. For each value in candidate configuration's initDataTypes attribute:
159 for (size_t i
= 0; i
< candidate
.initDataTypes
.size(); i
++) {
160 // 2.2.1. Let initDataType be the value.
161 const blink::WebString
& init_data_type
= candidate
.initDataTypes
[i
];
162 // 2.2.2. If initDataType is the empty string, return null.
163 if (init_data_type
.isEmpty())
164 return CONFIGURATION_NOT_SUPPORTED
;
165 // 2.2.3. If the implementation supports generating requests based on
166 // initDataType, add initDataType to supported types. String
167 // comparison is case-sensitive.
168 if (base::IsStringASCII(init_data_type
) &&
169 IsSupportedKeySystemWithInitDataType(
170 key_system
, base::UTF16ToASCII(init_data_type
))) {
171 supported_types
.push_back(init_data_type
);
175 // 2.3. If supported types is empty, return null.
176 if (supported_types
.empty())
177 return CONFIGURATION_NOT_SUPPORTED
;
179 // 2.4. Add supported types to accumulated configuration.
180 accumulated_configuration
->initDataTypes
= supported_types
;
183 // 3. Follow the steps for the value of candidate configuration's
184 // distinctiveIdentifier attribute from the following list:
185 // - "required": If the implementation does not support a persistent
186 // Distinctive Identifier in combination with accumulated configuration,
188 // - "optional": Continue.
189 // - "not-allowed": If the implementation requires a Distinctive
190 // Identifier in combination with accumulated configuration, return
192 EmeFeatureRequirement di_requirement
=
193 ConvertRequirement(candidate
.distinctiveIdentifier
);
194 if (!IsDistinctiveIdentifierRequirementSupported(key_system
, di_requirement
,
195 is_permission_possible
)) {
196 return CONFIGURATION_NOT_SUPPORTED
;
199 // 4. Add the value of the candidate configuration's distinctiveIdentifier
200 // attribute to accumulated configuration.
201 accumulated_configuration
->distinctiveIdentifier
=
202 candidate
.distinctiveIdentifier
;
204 // 5. Follow the steps for the value of candidate configuration's
205 // persistentState attribute from the following list:
206 // - "required": If the implementation does not support persisting state
207 // in combination with accumulated configuration, return null.
208 // - "optional": Continue.
209 // - "not-allowed": If the implementation requires persisting state in
210 // combination with accumulated configuration, return null.
211 EmeFeatureRequirement ps_requirement
=
212 ConvertRequirement(candidate
.persistentState
);
213 if (!IsPersistentStateRequirementSupported(key_system
, ps_requirement
,
214 is_permission_possible
)) {
215 return CONFIGURATION_NOT_SUPPORTED
;
218 // 6. Add the value of the candidate configuration's persistentState
219 // attribute to accumulated configuration.
220 accumulated_configuration
->persistentState
= candidate
.persistentState
;
222 // 7. If candidate configuration's videoCapabilities attribute is not empty,
223 // run the following steps:
224 if (!candidate
.videoCapabilities
.isEmpty()) {
225 // 7.1. Let video capabilities be the result of executing the Get Supported
226 // Capabilities for Media Type algorithm on Video, candidate
227 // configuration's videoCapabilities attribute, and accumulated
229 // 7.2. If video capabilities is null, return null.
230 std::vector
<blink::WebMediaKeySystemMediaCapability
> video_capabilities
;
231 if (!GetSupportedCapabilities(key_system
, candidate
.videoCapabilities
,
232 &video_capabilities
)) {
233 return CONFIGURATION_NOT_SUPPORTED
;
236 // 7.3. Add video capabilities to accumulated configuration.
237 accumulated_configuration
->videoCapabilities
= video_capabilities
;
240 // 8. If candidate configuration's audioCapabilities attribute is not empty,
241 // run the following steps:
242 if (!candidate
.audioCapabilities
.isEmpty()) {
243 // 8.1. Let audio capabilities be the result of executing the Get Supported
244 // Capabilities for Media Type algorithm on Audio, candidate
245 // configuration's audioCapabilities attribute, and accumulated
247 // 8.2. If audio capabilities is null, return null.
248 std::vector
<blink::WebMediaKeySystemMediaCapability
> audio_capabilities
;
249 if (!GetSupportedCapabilities(key_system
, candidate
.audioCapabilities
,
250 &audio_capabilities
)) {
251 return CONFIGURATION_NOT_SUPPORTED
;
254 // 8.3. Add audio capabilities to accumulated configuration.
255 accumulated_configuration
->audioCapabilities
= audio_capabilities
;
258 // 9. If accumulated configuration's distinctiveIdentifier value is
259 // "optional", follow the steps for the first matching condition from the
261 // - If the implementation requires a Distinctive Identifier for any of
262 // the combinations in accumulated configuration, change accumulated
263 // configuration's distinctiveIdentifier value to "required".
264 // - Otherwise, change accumulated configuration's distinctiveIdentifier
265 // value to "not-allowed".
266 // (Without robustness support, capabilities do not affect this.)
267 // TODO(sandersd): Implement robustness. http://crbug.com/442586
268 if (accumulated_configuration
->distinctiveIdentifier
==
269 blink::WebMediaKeySystemConfiguration::Requirement::Optional
) {
270 if (IsDistinctiveIdentifierRequirementSupported(
271 key_system
, EME_FEATURE_NOT_ALLOWED
, is_permission_possible
)) {
272 accumulated_configuration
->distinctiveIdentifier
=
273 blink::WebMediaKeySystemConfiguration::Requirement::NotAllowed
;
275 accumulated_configuration
->distinctiveIdentifier
=
276 blink::WebMediaKeySystemConfiguration::Requirement::Required
;
280 // 10. If accumulated configuration's persistentState value is "optional",
281 // follow the steps for the first matching condition from the following
283 // - If the implementation requires persisting state for any of the
284 // combinations in accumulated configuration, change accumulated
285 // configuration's persistentState value to "required".
286 // - Otherwise, change accumulated configuration's persistentState value
288 if (accumulated_configuration
->persistentState
==
289 blink::WebMediaKeySystemConfiguration::Requirement::Optional
) {
290 if (IsPersistentStateRequirementSupported(
291 key_system
, EME_FEATURE_NOT_ALLOWED
, is_permission_possible
)) {
292 accumulated_configuration
->persistentState
=
293 blink::WebMediaKeySystemConfiguration::Requirement::NotAllowed
;
295 accumulated_configuration
->persistentState
=
296 blink::WebMediaKeySystemConfiguration::Requirement::Required
;
300 // 11. If implementation in the configuration specified by the combination of
301 // the values in accumulated configuration is not supported or not allowed
302 // in the origin, return null.
304 ConvertRequirement(accumulated_configuration
->distinctiveIdentifier
);
305 if (!IsDistinctiveIdentifierRequirementSupported(key_system
, di_requirement
,
306 is_permission_granted
)) {
307 if (was_permission_requested
) {
308 // The optional permission was requested and denied.
309 // TODO(sandersd): Avoid the need for this logic - crbug.com/460616.
310 DCHECK(candidate
.distinctiveIdentifier
==
311 blink::WebMediaKeySystemConfiguration::Requirement::Optional
);
312 DCHECK(di_requirement
== EME_FEATURE_REQUIRED
);
313 DCHECK(!is_permission_granted
);
314 accumulated_configuration
->distinctiveIdentifier
=
315 blink::WebMediaKeySystemConfiguration::Requirement::NotAllowed
;
317 return CONFIGURATION_REQUIRES_PERMISSION
;
322 ConvertRequirement(accumulated_configuration
->persistentState
);
323 if (!IsPersistentStateRequirementSupported(key_system
, ps_requirement
,
324 is_permission_granted
)) {
325 DCHECK(!was_permission_requested
); // Should have failed at step 5.
326 return CONFIGURATION_REQUIRES_PERMISSION
;
329 // 12. Return accumulated configuration.
330 // (As an extra step, we record the available session types so that
331 // createSession() can be synchronous.)
332 std::vector
<blink::WebString
> session_types
;
333 session_types
.push_back(kTemporarySessionType
);
334 if (accumulated_configuration
->persistentState
==
335 blink::WebMediaKeySystemConfiguration::Requirement::Required
) {
336 if (IsPersistentLicenseSessionSupported(key_system
,
337 is_permission_granted
)) {
338 session_types
.push_back(kPersistentLicenseSessionType
);
340 if (IsPersistentReleaseMessageSessionSupported(key_system
,
341 is_permission_granted
)) {
342 session_types
.push_back(kPersistentReleaseMessageSessionType
);
345 accumulated_configuration
->sessionTypes
= session_types
;
347 return CONFIGURATION_SUPPORTED
;
350 // Report usage of key system to UMA. There are 2 different counts logged:
351 // 1. The key system is requested.
352 // 2. The requested key system and options are supported.
353 // Each stat is only reported once per renderer frame per key system.
354 // Note that WebEncryptedMediaClientImpl is only created once by each
356 class WebEncryptedMediaClientImpl::Reporter
{
358 enum KeySystemSupportStatus
{
359 KEY_SYSTEM_REQUESTED
= 0,
360 KEY_SYSTEM_SUPPORTED
= 1,
361 KEY_SYSTEM_SUPPORT_STATUS_COUNT
364 explicit Reporter(const std::string
& key_system_for_uma
)
365 : uma_name_(kKeySystemSupportUMAPrefix
+ key_system_for_uma
),
366 is_request_reported_(false),
367 is_support_reported_(false) {}
370 void ReportRequested() {
371 if (is_request_reported_
)
373 Report(KEY_SYSTEM_REQUESTED
);
374 is_request_reported_
= true;
377 void ReportSupported() {
378 DCHECK(is_request_reported_
);
379 if (is_support_reported_
)
381 Report(KEY_SYSTEM_SUPPORTED
);
382 is_support_reported_
= true;
386 void Report(KeySystemSupportStatus status
) {
387 // Not using UMA_HISTOGRAM_ENUMERATION directly because UMA_* macros
388 // require the names to be constant throughout the process' lifetime.
389 base::LinearHistogram::FactoryGet(
390 uma_name_
, 1, KEY_SYSTEM_SUPPORT_STATUS_COUNT
,
391 KEY_SYSTEM_SUPPORT_STATUS_COUNT
+ 1,
392 base::Histogram::kUmaTargetedHistogramFlag
)->Add(status
);
395 const std::string uma_name_
;
396 bool is_request_reported_
;
397 bool is_support_reported_
;
400 WebEncryptedMediaClientImpl::WebEncryptedMediaClientImpl(
401 scoped_ptr
<CdmFactory
> cdm_factory
,
402 MediaPermission
* media_permission
)
403 : cdm_factory_(cdm_factory
.Pass()), media_permission_(media_permission
),
404 weak_factory_(this) {
405 DCHECK(media_permission
);
408 WebEncryptedMediaClientImpl::~WebEncryptedMediaClientImpl() {
411 void WebEncryptedMediaClientImpl::requestMediaKeySystemAccess(
412 blink::WebEncryptedMediaRequest request
) {
413 // TODO(jrummell): This should be asynchronous, ideally not on the main
416 // Continued from requestMediaKeySystemAccess(), step 7, from
417 // https://w3c.github.io/encrypted-media/#requestmediakeysystemaccess
419 // 7.1. If keySystem is not one of the Key Systems supported by the user
420 // agent, reject promise with with a new DOMException whose name is
421 // NotSupportedError. String comparison is case-sensitive.
422 if (!base::IsStringASCII(request
.keySystem())) {
423 request
.requestNotSupported("Only ASCII keySystems are supported");
427 // Report this request to the UMA.
428 std::string key_system
= base::UTF16ToASCII(request
.keySystem());
429 GetReporter(key_system
)->ReportRequested();
431 if (!IsSupportedKeySystem(key_system
)) {
432 request
.requestNotSupported("Unsupported keySystem");
436 // 7.2-7.4. Implemented by SelectSupportedConfiguration().
437 SelectSupportedConfiguration(request
, false, false);
440 void WebEncryptedMediaClientImpl::SelectSupportedConfiguration(
441 blink::WebEncryptedMediaRequest request
,
442 bool was_permission_requested
,
443 bool is_permission_granted
) {
444 // Continued from requestMediaKeySystemAccess(), step 7.1, from
445 // https://w3c.github.io/encrypted-media/#requestmediakeysystemaccess
447 // 7.2. Let implementation be the implementation of keySystem.
448 std::string key_system
= base::UTF16ToASCII(request
.keySystem());
450 // 7.3. For each value in supportedConfigurations:
451 const blink::WebVector
<blink::WebMediaKeySystemConfiguration
>&
452 configurations
= request
.supportedConfigurations();
453 for (size_t i
= 0; i
< configurations
.size(); i
++) {
454 // 7.3.1. Let candidate configuration be the value.
455 const blink::WebMediaKeySystemConfiguration
& candidate_configuration
=
457 // 7.3.2. Let supported configuration be the result of executing the Get
458 // Supported Configuration algorithm on implementation, candidate
459 // configuration, and origin.
460 // 7.3.3. If supported configuration is not null, [initialize and return a
461 // new MediaKeySystemAccess object.]
462 blink::WebMediaKeySystemConfiguration accumulated_configuration
;
463 ConfigurationSupport supported
= GetSupportedConfiguration(
464 key_system
, candidate_configuration
, &accumulated_configuration
,
465 was_permission_requested
, is_permission_granted
);
467 case CONFIGURATION_NOT_SUPPORTED
:
469 case CONFIGURATION_REQUIRES_PERMISSION
:
470 DCHECK(!was_permission_requested
);
471 media_permission_
->RequestPermission(
472 MediaPermission::PROTECTED_MEDIA_IDENTIFIER
,
473 GURL(request
.securityOrigin().toString()),
474 // Try again with |was_permission_requested| true and
475 // |is_permission_granted| the value of the permission.
477 &WebEncryptedMediaClientImpl::SelectSupportedConfiguration
,
478 weak_factory_
.GetWeakPtr(), request
, true));
480 case CONFIGURATION_SUPPORTED
:
481 // Report that this request succeeded to the UMA.
482 GetReporter(key_system
)->ReportSupported();
483 request
.requestSucceeded(WebContentDecryptionModuleAccessImpl::Create(
484 request
.keySystem(), accumulated_configuration
,
485 request
.securityOrigin(), weak_factory_
.GetWeakPtr()));
490 // 7.4. Reject promise with a new DOMException whose name is
491 // NotSupportedError.
492 request
.requestNotSupported(
493 "None of the requested configurations were supported.");
496 void WebEncryptedMediaClientImpl::CreateCdm(
497 const blink::WebString
& key_system
,
498 const blink::WebSecurityOrigin
& security_origin
,
499 blink::WebContentDecryptionModuleResult result
) {
500 WebContentDecryptionModuleImpl::Create(cdm_factory_
.get(), security_origin
,
504 // Lazily create Reporters.
505 WebEncryptedMediaClientImpl::Reporter
* WebEncryptedMediaClientImpl::GetReporter(
506 const std::string
& key_system
) {
507 std::string uma_name
= GetKeySystemNameForUMA(key_system
);
508 Reporter
* reporter
= reporters_
.get(uma_name
);
509 if (reporter
!= nullptr)
512 // Reporter not found, so create one.
514 reporters_
.add(uma_name
, make_scoped_ptr(new Reporter(uma_name
)));
515 DCHECK(result
.second
);
516 return result
.first
->second
;