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 "content/browser/media/cdm/browser_cdm_manager.h"
10 #include "base/lazy_instance.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/task_runner.h"
13 #include "content/common/media/cdm_messages.h"
14 #include "content/public/browser/browser_context.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/content_browser_client.h"
17 #include "content/public/browser/permission_manager.h"
18 #include "content/public/browser/permission_type.h"
19 #include "content/public/browser/render_frame_host.h"
20 #include "content/public/browser/render_process_host.h"
21 #include "content/public/browser/render_view_host.h"
22 #include "content/public/browser/web_contents.h"
23 #include "media/base/browser_cdm.h"
24 #include "media/base/browser_cdm_factory.h"
25 #include "media/base/cdm_promise.h"
26 #include "media/base/limits.h"
28 #if defined(OS_ANDROID)
29 #include "content/public/common/renderer_preferences.h"
34 using media::BrowserCdm
;
35 using media::MediaKeys
;
39 // The ID used in this class is a concatenation of |render_frame_id| and
40 // |cdm_id|, i.e. (render_frame_id << 32) + cdm_id.
42 uint64
GetId(int render_frame_id
, int cdm_id
) {
43 return (static_cast<uint64
>(render_frame_id
) << 32) +
44 static_cast<uint64
>(cdm_id
);
47 bool IdBelongsToFrame(uint64 id
, int render_frame_id
) {
48 return (id
>> 32) == static_cast<uint64
>(render_frame_id
);
51 // media::CdmPromiseTemplate implementation backed by a BrowserCdmManager.
52 template <typename
... T
>
53 class CdmPromiseInternal
: public media::CdmPromiseTemplate
<T
...> {
55 CdmPromiseInternal(BrowserCdmManager
* manager
,
60 render_frame_id_(render_frame_id
),
62 promise_id_(promise_id
) {
66 ~CdmPromiseInternal() final
{}
68 // CdmPromiseTemplate<> implementation.
69 void resolve(const T
&... result
) final
;
71 void reject(MediaKeys::Exception exception
,
73 const std::string
& error_message
) final
{
75 manager_
->RejectPromise(render_frame_id_
, cdm_id_
, promise_id_
, exception
,
76 system_code
, error_message
);
80 using media::CdmPromiseTemplate
<T
...>::MarkPromiseSettled
;
82 BrowserCdmManager
* const manager_
;
83 const int render_frame_id_
;
85 const uint32_t promise_id_
;
89 void CdmPromiseInternal
<>::resolve() {
91 manager_
->ResolvePromise(render_frame_id_
, cdm_id_
, promise_id_
);
95 void CdmPromiseInternal
<std::string
>::resolve(const std::string
& session_id
) {
97 manager_
->ResolvePromiseWithSession(render_frame_id_
, cdm_id_
, promise_id_
,
101 typedef CdmPromiseInternal
<> SimplePromise
;
102 typedef CdmPromiseInternal
<std::string
> NewSessionPromise
;
106 // Render process ID to BrowserCdmManager map.
107 typedef std::map
<int, BrowserCdmManager
*> BrowserCdmManagerMap
;
108 base::LazyInstance
<BrowserCdmManagerMap
> g_browser_cdm_manager_map
=
109 LAZY_INSTANCE_INITIALIZER
;
111 BrowserCdmManager
* BrowserCdmManager::FromProcess(int render_process_id
) {
112 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
114 if (!g_browser_cdm_manager_map
.Get().count(render_process_id
))
117 return g_browser_cdm_manager_map
.Get()[render_process_id
];
120 BrowserCdmManager::BrowserCdmManager(
121 int render_process_id
,
122 const scoped_refptr
<base::TaskRunner
>& task_runner
)
123 : BrowserMessageFilter(CdmMsgStart
),
124 render_process_id_(render_process_id
),
125 task_runner_(task_runner
),
126 weak_ptr_factory_(this) {
127 DVLOG(1) << __FUNCTION__
<< ": " << render_process_id_
;
128 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
130 if (!task_runner_
.get()) {
132 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI
);
135 DCHECK(!g_browser_cdm_manager_map
.Get().count(render_process_id_
))
136 << render_process_id_
;
137 g_browser_cdm_manager_map
.Get()[render_process_id
] = this;
140 BrowserCdmManager::~BrowserCdmManager() {
141 DVLOG(1) << __FUNCTION__
<< ": " << render_process_id_
;
142 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
143 DCHECK(g_browser_cdm_manager_map
.Get().count(render_process_id_
));
144 DCHECK_EQ(this, g_browser_cdm_manager_map
.Get()[render_process_id_
]);
146 g_browser_cdm_manager_map
.Get().erase(render_process_id_
);
149 // Makes sure BrowserCdmManager is always deleted on the Browser UI thread.
150 void BrowserCdmManager::OnDestruct() const {
151 DVLOG(1) << __FUNCTION__
<< ": " << render_process_id_
;
152 if (BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
155 BrowserThread::DeleteSoon(BrowserThread::UI
, FROM_HERE
, this);
159 base::TaskRunner
* BrowserCdmManager::OverrideTaskRunnerForMessage(
160 const IPC::Message
& message
) {
161 // Only handles CDM messages.
162 if (IPC_MESSAGE_CLASS(message
) != CdmMsgStart
)
165 return task_runner_
.get();
168 bool BrowserCdmManager::OnMessageReceived(const IPC::Message
& msg
) {
169 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
171 IPC_BEGIN_MESSAGE_MAP(BrowserCdmManager
, msg
)
172 IPC_MESSAGE_HANDLER(CdmHostMsg_InitializeCdm
, OnInitializeCdm
)
173 IPC_MESSAGE_HANDLER(CdmHostMsg_SetServerCertificate
, OnSetServerCertificate
)
174 IPC_MESSAGE_HANDLER(CdmHostMsg_CreateSessionAndGenerateRequest
,
175 OnCreateSessionAndGenerateRequest
)
176 IPC_MESSAGE_HANDLER(CdmHostMsg_UpdateSession
, OnUpdateSession
)
177 IPC_MESSAGE_HANDLER(CdmHostMsg_CloseSession
, OnCloseSession
)
178 IPC_MESSAGE_HANDLER(CdmHostMsg_DestroyCdm
, OnDestroyCdm
)
179 IPC_MESSAGE_UNHANDLED(handled
= false)
180 IPC_END_MESSAGE_MAP()
184 media::BrowserCdm
* BrowserCdmManager::GetCdm(int render_frame_id
,
186 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
187 return cdm_map_
.get(GetId(render_frame_id
, cdm_id
));
190 void BrowserCdmManager::RenderFrameDeleted(int render_frame_id
) {
191 if (!task_runner_
->RunsTasksOnCurrentThread()) {
192 task_runner_
->PostTask(
194 base::Bind(&BrowserCdmManager::RemoveAllCdmForFrame
,
195 this, render_frame_id
));
198 RemoveAllCdmForFrame(render_frame_id
);
201 void BrowserCdmManager::ResolvePromise(int render_frame_id
,
203 uint32_t promise_id
) {
204 Send(new CdmMsg_ResolvePromise(render_frame_id
, cdm_id
, promise_id
));
207 void BrowserCdmManager::ResolvePromiseWithSession(
211 const std::string
& session_id
) {
212 if (session_id
.length() > media::limits::kMaxSessionIdLength
) {
213 RejectPromise(render_frame_id
, cdm_id
, promise_id
,
214 MediaKeys::INVALID_ACCESS_ERROR
, 0,
215 "Session ID is too long.");
219 Send(new CdmMsg_ResolvePromiseWithSession(render_frame_id
, cdm_id
, promise_id
,
223 void BrowserCdmManager::RejectPromise(int render_frame_id
,
226 media::MediaKeys::Exception exception
,
227 uint32_t system_code
,
228 const std::string
& error_message
) {
229 Send(new CdmMsg_RejectPromise(render_frame_id
, cdm_id
, promise_id
, exception
,
230 system_code
, error_message
));
233 void BrowserCdmManager::OnSessionMessage(
236 const std::string
& session_id
,
237 media::MediaKeys::MessageType message_type
,
238 const std::vector
<uint8
>& message
,
239 const GURL
& legacy_destination_url
) {
240 GURL verified_gurl
= legacy_destination_url
;
241 if (!verified_gurl
.is_valid() && !verified_gurl
.is_empty()) {
242 DLOG(WARNING
) << "SessionMessage legacy_destination_url is invalid : "
243 << legacy_destination_url
.possibly_invalid_spec();
245 GURL::EmptyGURL(); // Replace invalid legacy_destination_url.
248 Send(new CdmMsg_SessionMessage(render_frame_id
, cdm_id
, session_id
,
249 message_type
, message
, verified_gurl
));
252 void BrowserCdmManager::OnSessionClosed(int render_frame_id
,
254 const std::string
& session_id
) {
255 Send(new CdmMsg_SessionClosed(render_frame_id
, cdm_id
, session_id
));
258 void BrowserCdmManager::OnLegacySessionError(
261 const std::string
& session_id
,
262 MediaKeys::Exception exception_code
,
264 const std::string
& error_message
) {
265 Send(new CdmMsg_LegacySessionError(render_frame_id
, cdm_id
, session_id
,
266 exception_code
, system_code
,
270 void BrowserCdmManager::OnSessionKeysChange(int render_frame_id
,
272 const std::string
& session_id
,
273 bool has_additional_usable_key
,
274 media::CdmKeysInfo keys_info
) {
275 std::vector
<media::CdmKeyInformation
> key_info_vector
;
276 for (const auto& key_info
: keys_info
)
277 key_info_vector
.push_back(*key_info
);
278 Send(new CdmMsg_SessionKeysChange(render_frame_id
, cdm_id
, session_id
,
279 has_additional_usable_key
,
283 void BrowserCdmManager::OnSessionExpirationUpdate(
286 const std::string
& session_id
,
287 const base::Time
& new_expiry_time
) {
288 Send(new CdmMsg_SessionExpirationUpdate(render_frame_id
, cdm_id
, session_id
,
292 void BrowserCdmManager::OnInitializeCdm(int render_frame_id
,
294 const std::string
& key_system
,
295 const GURL
& security_origin
) {
296 if (key_system
.size() > media::limits::kMaxKeySystemLength
) {
297 // This failure will be discovered and reported by OnCreateSession()
298 // as GetCdm() will return null.
299 NOTREACHED() << "Invalid key system: " << key_system
;
303 AddCdm(render_frame_id
, cdm_id
, key_system
, security_origin
);
306 void BrowserCdmManager::OnSetServerCertificate(
310 const std::vector
<uint8_t>& certificate
) {
311 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
313 scoped_ptr
<SimplePromise
> promise(
314 new SimplePromise(this, render_frame_id
, cdm_id
, promise_id
));
316 BrowserCdm
* cdm
= GetCdm(render_frame_id
, cdm_id
);
318 promise
->reject(MediaKeys::INVALID_STATE_ERROR
, 0, "CDM not found.");
322 if (certificate
.empty()) {
323 promise
->reject(MediaKeys::INVALID_ACCESS_ERROR
, 0, "Empty certificate.");
327 cdm
->SetServerCertificate(certificate
, promise
.Pass());
330 void BrowserCdmManager::OnCreateSessionAndGenerateRequest(
334 CdmHostMsg_CreateSession_InitDataType init_data_type
,
335 const std::vector
<uint8
>& init_data
) {
336 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
338 scoped_ptr
<NewSessionPromise
> promise(
339 new NewSessionPromise(this, render_frame_id
, cdm_id
, promise_id
));
341 if (init_data
.size() > media::limits::kMaxInitDataLength
) {
342 LOG(WARNING
) << "InitData for ID: " << cdm_id
343 << " too long: " << init_data
.size();
344 promise
->reject(MediaKeys::INVALID_ACCESS_ERROR
, 0, "Init data too long.");
348 media::EmeInitDataType eme_init_data_type
;
349 switch (init_data_type
) {
350 case INIT_DATA_TYPE_WEBM
:
351 eme_init_data_type
= media::EmeInitDataType::WEBM
;
353 #if defined(USE_PROPRIETARY_CODECS)
354 case INIT_DATA_TYPE_CENC
:
355 eme_init_data_type
= media::EmeInitDataType::CENC
;
360 promise
->reject(MediaKeys::INVALID_ACCESS_ERROR
, 0,
361 "Invalid init data type.");
365 BrowserCdm
* cdm
= GetCdm(render_frame_id
, cdm_id
);
367 DLOG(WARNING
) << "No CDM found for: " << render_frame_id
<< ", " << cdm_id
;
368 promise
->reject(MediaKeys::INVALID_STATE_ERROR
, 0, "CDM not found.");
372 CheckPermissionStatus(
373 render_frame_id
, cdm_id
,
374 base::Bind(&BrowserCdmManager::CreateSessionAndGenerateRequestIfPermitted
,
375 this, render_frame_id
, cdm_id
, eme_init_data_type
, init_data
,
376 base::Passed(&promise
)));
379 void BrowserCdmManager::OnUpdateSession(int render_frame_id
,
382 const std::string
& session_id
,
383 const std::vector
<uint8
>& response
) {
384 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
386 scoped_ptr
<SimplePromise
> promise(
387 new SimplePromise(this, render_frame_id
, cdm_id
, promise_id
));
389 BrowserCdm
* cdm
= GetCdm(render_frame_id
, cdm_id
);
391 promise
->reject(MediaKeys::INVALID_STATE_ERROR
, 0, "CDM not found.");
395 if (response
.size() > media::limits::kMaxSessionResponseLength
) {
396 LOG(WARNING
) << "Response for ID " << cdm_id
397 << " is too long: " << response
.size();
398 promise
->reject(MediaKeys::INVALID_ACCESS_ERROR
, 0, "Response too long.");
402 if (response
.empty()) {
403 promise
->reject(MediaKeys::INVALID_ACCESS_ERROR
, 0, "Response is empty.");
407 cdm
->UpdateSession(session_id
, response
, promise
.Pass());
410 void BrowserCdmManager::OnCloseSession(int render_frame_id
,
413 const std::string
& session_id
) {
414 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
416 scoped_ptr
<SimplePromise
> promise(
417 new SimplePromise(this, render_frame_id
, cdm_id
, promise_id
));
419 BrowserCdm
* cdm
= GetCdm(render_frame_id
, cdm_id
);
421 promise
->reject(MediaKeys::INVALID_STATE_ERROR
, 0, "CDM not found.");
425 cdm
->CloseSession(session_id
, promise
.Pass());
428 void BrowserCdmManager::OnDestroyCdm(int render_frame_id
, int cdm_id
) {
429 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
430 RemoveCdm(GetId(render_frame_id
, cdm_id
));
433 // Use a weak pointer here instead of |this| to avoid circular references.
434 #define BROWSER_CDM_MANAGER_CB(func) \
435 base::Bind(&BrowserCdmManager::func, weak_ptr_factory_.GetWeakPtr(), \
436 render_frame_id, cdm_id)
438 void BrowserCdmManager::AddCdm(int render_frame_id
,
440 const std::string
& key_system
,
441 const GURL
& security_origin
) {
442 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
443 DCHECK(!GetCdm(render_frame_id
, cdm_id
));
445 bool use_secure_surface
= false;
447 #if defined(OS_ANDROID)
448 // TODO(sandersd): Pass the security level from key system instead.
449 // http://crbug.com/467779
450 RenderFrameHost
* rfh
=
451 RenderFrameHost::FromID(render_process_id_
, render_frame_id
);
452 WebContents
* web_contents
= WebContents::FromRenderFrameHost(rfh
);
454 content::RendererPreferences
* prefs
=
455 web_contents
->GetMutableRendererPrefs();
456 use_secure_surface
= prefs
->use_video_overlay_for_embedded_encrypted_video
;
460 scoped_ptr
<BrowserCdm
> cdm(media::CreateBrowserCdm(
461 key_system
, use_secure_surface
, BROWSER_CDM_MANAGER_CB(OnSessionMessage
),
462 BROWSER_CDM_MANAGER_CB(OnSessionClosed
),
463 BROWSER_CDM_MANAGER_CB(OnLegacySessionError
),
464 BROWSER_CDM_MANAGER_CB(OnSessionKeysChange
),
465 BROWSER_CDM_MANAGER_CB(OnSessionExpirationUpdate
)));
468 // This failure will be discovered and reported by
469 // OnCreateSessionAndGenerateRequest() as GetCdm() will return null.
470 DVLOG(1) << "failed to create CDM.";
474 uint64 id
= GetId(render_frame_id
, cdm_id
);
475 cdm_map_
.add(id
, cdm
.Pass());
476 cdm_security_origin_map_
[id
] = security_origin
;
479 void BrowserCdmManager::RemoveAllCdmForFrame(int render_frame_id
) {
480 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
482 std::vector
<uint64
> ids_to_remove
;
483 for (CdmMap::iterator it
= cdm_map_
.begin(); it
!= cdm_map_
.end(); ++it
) {
484 if (IdBelongsToFrame(it
->first
, render_frame_id
))
485 ids_to_remove
.push_back(it
->first
);
488 for (size_t i
= 0; i
< ids_to_remove
.size(); ++i
)
489 RemoveCdm(ids_to_remove
[i
]);
492 void BrowserCdmManager::RemoveCdm(uint64 id
) {
493 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
496 cdm_security_origin_map_
.erase(id
);
499 void BrowserCdmManager::CheckPermissionStatus(
502 const PermissionStatusCB
& permission_status_cb
) {
503 // Always called on |task_runner_|, which may not be on the UI thread.
504 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
506 GURL security_origin
;
507 std::map
<uint64
, GURL
>::const_iterator iter
=
508 cdm_security_origin_map_
.find(GetId(render_frame_id
, cdm_id
));
509 DCHECK(iter
!= cdm_security_origin_map_
.end());
510 if (iter
!= cdm_security_origin_map_
.end())
511 security_origin
= iter
->second
;
513 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
)) {
514 BrowserThread::PostTask(
515 BrowserThread::UI
, FROM_HERE
,
516 base::Bind(&BrowserCdmManager::CheckPermissionStatusOnUIThread
, this,
517 render_frame_id
, security_origin
, permission_status_cb
));
519 CheckPermissionStatusOnUIThread(render_frame_id
, security_origin
,
520 permission_status_cb
);
524 // Note: This function runs on the UI thread, which may be different from
525 // |task_runner_|. Be careful about thread safety!
526 void BrowserCdmManager::CheckPermissionStatusOnUIThread(
528 const GURL
& security_origin
,
529 const base::Callback
<void(bool)>& permission_status_cb
) {
530 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
532 RenderFrameHost
* rfh
=
533 RenderFrameHost::FromID(render_process_id_
, render_frame_id
);
534 WebContents
* web_contents
= WebContents::FromRenderFrameHost(rfh
);
535 PermissionManager
* permission_manager
=
536 web_contents
->GetBrowserContext()->GetPermissionManager();
537 if (!permission_manager
) {
538 permission_status_cb
.Run(false);
542 PermissionStatus permission_status
= permission_manager
->GetPermissionStatus(
543 content::PermissionType::PROTECTED_MEDIA_IDENTIFIER
,
545 web_contents
->GetLastCommittedURL().GetOrigin());
547 bool allowed
= (permission_status
== PERMISSION_STATUS_GRANTED
);
548 if (!task_runner_
->RunsTasksOnCurrentThread()) {
549 task_runner_
->PostTask(FROM_HERE
,
550 base::Bind(permission_status_cb
, allowed
));
552 permission_status_cb
.Run(allowed
);
556 void BrowserCdmManager::CreateSessionAndGenerateRequestIfPermitted(
559 media::EmeInitDataType init_data_type
,
560 const std::vector
<uint8
>& init_data
,
561 scoped_ptr
<media::NewSessionCdmPromise
> promise
,
562 bool permission_was_allowed
) {
563 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
565 if (!permission_was_allowed
) {
566 promise
->reject(MediaKeys::NOT_SUPPORTED_ERROR
, 0, "Permission denied.");
570 BrowserCdm
* cdm
= GetCdm(render_frame_id
, cdm_id
);
572 promise
->reject(MediaKeys::INVALID_STATE_ERROR
, 0, "CDM not found.");
576 // Only the temporary session type is supported in browser CDM path.
577 // TODO(xhwang): Add SessionType support if needed.
578 cdm
->CreateSessionAndGenerateRequest(media::MediaKeys::TEMPORARY_SESSION
,
579 init_data_type
, init_data
,
583 } // namespace content