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"
8 #include "base/command_line.h"
9 #include "base/lazy_instance.h"
10 #include "base/task_runner.h"
11 #include "content/common/media/cdm_messages.h"
12 #include "content/public/browser/browser_thread.h"
13 #include "content/public/browser/content_browser_client.h"
14 #include "content/public/browser/render_frame_host.h"
15 #include "content/public/browser/render_process_host.h"
16 #include "content/public/browser/render_view_host.h"
17 #include "content/public/browser/web_contents.h"
18 #include "content/public/common/content_switches.h"
19 #include "media/base/browser_cdm.h"
20 #include "media/base/browser_cdm_factory.h"
21 #include "media/base/media_switches.h"
25 using media::BrowserCdm;
26 using media::MediaKeys;
28 // Maximum lengths for various EME API parameters. These are checks to
29 // prevent unnecessarily large parameters from being passed around, and the
30 // lengths are somewhat arbitrary as the EME spec doesn't specify any limits.
31 const size_t kMaxInitDataLength = 64 * 1024; // 64 KB
32 const size_t kMaxSessionResponseLength = 64 * 1024; // 64 KB
33 const size_t kMaxKeySystemLength = 256;
35 // The ID used in this class is a concatenation of |render_frame_id| and
36 // |cdm_id|, i.e. (render_frame_id << 32) + cdm_id.
38 static uint64 GetId(int render_frame_id, int cdm_id) {
39 return (static_cast<uint64>(render_frame_id) << 32) +
40 static_cast<uint64>(cdm_id);
43 static bool IdBelongsToFrame(uint64 id, int render_frame_id) {
44 return (id >> 32) == static_cast<uint64>(render_frame_id);
47 // Render process ID to BrowserCdmManager map.
48 typedef std::map<int, BrowserCdmManager*> BrowserCdmManagerMap;
49 base::LazyInstance<BrowserCdmManagerMap> g_browser_cdm_manager_map =
50 LAZY_INSTANCE_INITIALIZER;
52 BrowserCdmManager* BrowserCdmManager::FromProcess(int render_process_id) {
53 DCHECK_CURRENTLY_ON(BrowserThread::UI);
55 if (!g_browser_cdm_manager_map.Get().count(render_process_id))
58 return g_browser_cdm_manager_map.Get()[render_process_id];
61 BrowserCdmManager::BrowserCdmManager(
62 int render_process_id,
63 const scoped_refptr<base::TaskRunner>& task_runner)
64 : BrowserMessageFilter(CdmMsgStart),
65 render_process_id_(render_process_id),
66 task_runner_(task_runner) {
67 DCHECK_CURRENTLY_ON(BrowserThread::UI);
69 if (!task_runner_.get()) {
71 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI);
74 // This may overwrite an existing entry of |render_process_id| if the
75 // previous process crashed and didn't cleanup its child frames. For example,
76 // see FrameTreeBrowserTest.FrameTreeAfterCrash test.
77 g_browser_cdm_manager_map.Get()[render_process_id] = this;
80 BrowserCdmManager::~BrowserCdmManager() {
81 DCHECK_CURRENTLY_ON(BrowserThread::UI);
82 DCHECK(g_browser_cdm_manager_map.Get().count(render_process_id_));
84 g_browser_cdm_manager_map.Get().erase(render_process_id_);
87 // Makes sure BrowserCdmManager is always deleted on the Browser UI thread.
88 void BrowserCdmManager::OnDestruct() const {
89 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
92 BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
96 base::TaskRunner* BrowserCdmManager::OverrideTaskRunnerForMessage(
97 const IPC::Message& message) {
98 // Only handles CDM messages.
99 if (IPC_MESSAGE_CLASS(message) != CdmMsgStart)
102 return task_runner_.get();
105 bool BrowserCdmManager::OnMessageReceived(const IPC::Message& msg) {
106 DCHECK(task_runner_->RunsTasksOnCurrentThread());
108 IPC_BEGIN_MESSAGE_MAP(BrowserCdmManager, msg)
109 IPC_MESSAGE_HANDLER(CdmHostMsg_InitializeCdm, OnInitializeCdm)
110 IPC_MESSAGE_HANDLER(CdmHostMsg_CreateSession, OnCreateSession)
111 IPC_MESSAGE_HANDLER(CdmHostMsg_UpdateSession, OnUpdateSession)
112 IPC_MESSAGE_HANDLER(CdmHostMsg_ReleaseSession, OnReleaseSession)
113 IPC_MESSAGE_HANDLER(CdmHostMsg_DestroyCdm, OnDestroyCdm)
114 IPC_MESSAGE_UNHANDLED(handled = false)
115 IPC_END_MESSAGE_MAP()
119 media::BrowserCdm* BrowserCdmManager::GetCdm(int render_frame_id, int cdm_id) {
120 DCHECK(task_runner_->RunsTasksOnCurrentThread());
121 return cdm_map_.get(GetId(render_frame_id, cdm_id));
124 void BrowserCdmManager::RenderFrameDeleted(int render_frame_id) {
125 DCHECK(task_runner_->RunsTasksOnCurrentThread());
127 std::vector<uint64> ids_to_remove;
128 for (CdmMap::iterator it = cdm_map_.begin(); it != cdm_map_.end(); ++it) {
129 if (IdBelongsToFrame(it->first, render_frame_id))
130 ids_to_remove.push_back(it->first);
133 for (size_t i = 0; i < ids_to_remove.size(); ++i)
134 RemoveCdm(ids_to_remove[i]);
137 void BrowserCdmManager::OnSessionCreated(int render_frame_id,
140 const std::string& web_session_id) {
141 Send(new CdmMsg_SessionCreated(
142 render_frame_id, cdm_id, session_id, web_session_id));
145 void BrowserCdmManager::OnSessionMessage(int render_frame_id,
148 const std::vector<uint8>& message,
149 const GURL& destination_url) {
150 GURL verified_gurl = destination_url;
151 if (!verified_gurl.is_valid() && !verified_gurl.is_empty()) {
152 DLOG(WARNING) << "SessionMessage destination_url is invalid : "
153 << destination_url.possibly_invalid_spec();
154 verified_gurl = GURL::EmptyGURL(); // Replace invalid destination_url.
157 Send(new CdmMsg_SessionMessage(
158 render_frame_id, cdm_id, session_id, message, verified_gurl));
161 void BrowserCdmManager::OnSessionReady(int render_frame_id,
164 Send(new CdmMsg_SessionReady(render_frame_id, cdm_id, session_id));
167 void BrowserCdmManager::OnSessionClosed(int render_frame_id,
170 Send(new CdmMsg_SessionClosed(render_frame_id, cdm_id, session_id));
173 void BrowserCdmManager::OnSessionError(int render_frame_id,
176 MediaKeys::KeyError error_code,
177 uint32 system_code) {
178 Send(new CdmMsg_SessionError(
179 render_frame_id, cdm_id, session_id, error_code, system_code));
182 void BrowserCdmManager::OnInitializeCdm(int render_frame_id,
184 const std::string& key_system,
185 const GURL& security_origin) {
186 if (key_system.size() > kMaxKeySystemLength) {
187 // This failure will be discovered and reported by OnCreateSession()
188 // as GetCdm() will return null.
189 NOTREACHED() << "Invalid key system: " << key_system;
193 AddCdm(render_frame_id, cdm_id, key_system, security_origin);
196 void BrowserCdmManager::OnCreateSession(
200 CdmHostMsg_CreateSession_ContentType content_type,
201 const std::vector<uint8>& init_data) {
202 if (init_data.size() > kMaxInitDataLength) {
203 LOG(WARNING) << "InitData for ID: " << cdm_id
204 << " too long: " << init_data.size();
205 SendSessionError(render_frame_id, cdm_id, session_id);
209 // Convert the session content type into a MIME type. "audio" and "video"
210 // don't matter, so using "video" for the MIME type.
212 // https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#dom-createsession
213 std::string mime_type;
214 switch (content_type) {
215 case CREATE_SESSION_TYPE_WEBM:
216 mime_type = "video/webm";
218 case CREATE_SESSION_TYPE_MP4:
219 mime_type = "video/mp4";
226 #if defined(OS_ANDROID)
227 if (CommandLine::ForCurrentProcess()
228 ->HasSwitch(switches::kDisableInfobarForProtectedMediaIdentifier)) {
229 CreateSessionIfPermitted(
230 render_frame_id, cdm_id, session_id, mime_type, init_data, true);
235 BrowserCdm* cdm = GetCdm(render_frame_id, cdm_id);
237 DLOG(WARNING) << "No CDM found for: " << render_frame_id << ", " << cdm_id;
238 SendSessionError(render_frame_id, cdm_id, session_id);
242 std::map<uint64, GURL>::const_iterator iter =
243 cdm_security_origin_map_.find(GetId(render_frame_id, cdm_id));
244 if (iter == cdm_security_origin_map_.end()) {
246 SendSessionError(render_frame_id, cdm_id, session_id);
249 GURL security_origin = iter->second;
251 RenderFrameHost* rfh =
252 RenderFrameHost::FromID(render_process_id_, render_frame_id);
253 WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
254 DCHECK(web_contents);
255 GetContentClient()->browser()->RequestPermission(
256 content::PERMISSION_PROTECTED_MEDIA,
260 // Only implemented for Android infobars which do not support
263 base::Bind(&BrowserCdmManager::CreateSessionIfPermitted,
272 void BrowserCdmManager::OnUpdateSession(
276 const std::vector<uint8>& response) {
277 BrowserCdm* cdm = GetCdm(render_frame_id, cdm_id);
279 DLOG(WARNING) << "No CDM found for: " << render_frame_id << ", " << cdm_id;
280 SendSessionError(render_frame_id, cdm_id, session_id);
284 if (response.size() > kMaxSessionResponseLength) {
285 LOG(WARNING) << "Response for ID " << cdm_id
286 << " is too long: " << response.size();
287 SendSessionError(render_frame_id, cdm_id, session_id);
291 cdm->UpdateSession(session_id, &response[0], response.size());
294 void BrowserCdmManager::OnReleaseSession(int render_frame_id,
297 BrowserCdm* cdm = GetCdm(render_frame_id, cdm_id);
299 DLOG(WARNING) << "No CDM found for: " << render_frame_id << ", " << cdm_id;
300 SendSessionError(render_frame_id, cdm_id, session_id);
304 cdm->ReleaseSession(session_id);
307 void BrowserCdmManager::OnDestroyCdm(int render_frame_id, int cdm_id) {
308 RemoveCdm(GetId(render_frame_id, cdm_id));
311 void BrowserCdmManager::SendSessionError(int render_frame_id,
315 render_frame_id, cdm_id, session_id, MediaKeys::kUnknownError, 0);
318 #define BROWSER_CDM_MANAGER_CB(func) \
319 base::Bind(&BrowserCdmManager::func, this, render_frame_id, cdm_id)
321 void BrowserCdmManager::AddCdm(int render_frame_id,
323 const std::string& key_system,
324 const GURL& security_origin) {
325 DCHECK(task_runner_->RunsTasksOnCurrentThread());
326 DCHECK(!GetCdm(render_frame_id, cdm_id));
328 scoped_ptr<BrowserCdm> cdm(
329 media::CreateBrowserCdm(key_system,
330 BROWSER_CDM_MANAGER_CB(OnSessionCreated),
331 BROWSER_CDM_MANAGER_CB(OnSessionMessage),
332 BROWSER_CDM_MANAGER_CB(OnSessionReady),
333 BROWSER_CDM_MANAGER_CB(OnSessionClosed),
334 BROWSER_CDM_MANAGER_CB(OnSessionError)));
337 // This failure will be discovered and reported by OnCreateSession()
338 // as GetCdm() will return null.
339 DVLOG(1) << "failed to create CDM.";
343 uint64 id = GetId(render_frame_id, cdm_id);
344 cdm_map_.add(id, cdm.Pass());
345 cdm_security_origin_map_[id] = security_origin;
348 void BrowserCdmManager::RemoveCdm(uint64 id) {
349 DCHECK(task_runner_->RunsTasksOnCurrentThread());
352 cdm_security_origin_map_.erase(id);
353 if (cdm_cancel_permission_map_.count(id)) {
354 cdm_cancel_permission_map_[id].Run();
355 cdm_cancel_permission_map_.erase(id);
359 void BrowserCdmManager::CreateSessionIfPermitted(
363 const std::string& content_type,
364 const std::vector<uint8>& init_data,
366 cdm_cancel_permission_map_.erase(GetId(render_frame_id, cdm_id));
368 SendSessionError(render_frame_id, cdm_id, session_id);
372 BrowserCdm* cdm = GetCdm(render_frame_id, cdm_id);
374 DLOG(WARNING) << "No CDM found for: " << render_frame_id << ", " << cdm_id;
375 SendSessionError(render_frame_id, cdm_id, session_id);
379 // This could fail, in which case a SessionError will be fired.
380 cdm->CreateSession(session_id, content_type, &init_data[0], init_data.size());
383 } // namespace content