Fix crash on app list start page contents not existing.
[chromium-blink-merge.git] / media / blink / webencryptedmediaclient_impl.cc
blob2dd051e59626ee9d47fcfd33c11c63a74c23dd3c
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"
7 #include "base/bind.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"
22 namespace media {
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,
56 key_system)) {
57 return false;
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
66 codec_vector.clear();
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>&
74 capabilities,
75 std::vector<blink::WebMediaKeySystemMediaCapability>*
76 media_type_capabilities) {
77 // From
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())
90 return false;
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))) {
97 continue;
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.";
105 continue;
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;
133 NOTREACHED();
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,
187 // return null.
188 // - "optional": Continue.
189 // - "not-allowed": If the implementation requires a Distinctive
190 // Identifier in combination with accumulated configuration, return
191 // null.
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
228 // configuration.
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
246 // configuration.
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
260 // following list:
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;
274 } else {
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
282 // list:
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
287 // to "not-allowed".
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;
294 } else {
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.
303 di_requirement =
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;
316 } else {
317 return CONFIGURATION_REQUIRES_PERMISSION;
321 ps_requirement =
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
355 // renderer frame.
356 class WebEncryptedMediaClientImpl::Reporter {
357 public:
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) {}
368 ~Reporter() {}
370 void ReportRequested() {
371 if (is_request_reported_)
372 return;
373 Report(KEY_SYSTEM_REQUESTED);
374 is_request_reported_ = true;
377 void ReportSupported() {
378 DCHECK(is_request_reported_);
379 if (is_support_reported_)
380 return;
381 Report(KEY_SYSTEM_SUPPORTED);
382 is_support_reported_ = true;
385 private:
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
414 // thread.
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");
424 return;
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");
433 return;
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 =
456 configurations[i];
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);
466 switch (supported) {
467 case CONFIGURATION_NOT_SUPPORTED:
468 continue;
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.
476 base::Bind(
477 &WebEncryptedMediaClientImpl::SelectSupportedConfiguration,
478 weak_factory_.GetWeakPtr(), request, true));
479 return;
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()));
486 return;
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,
501 key_system, result);
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)
510 return reporter;
512 // Reporter not found, so create one.
513 auto result =
514 reporters_.add(uma_name, make_scoped_ptr(new Reporter(uma_name)));
515 DCHECK(result.second);
516 return result.first->second;
519 } // namespace media