| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/media/cdm/browser_cdm_manager.h" |
| |
| #include <stddef.h> |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/lazy_instance.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/task_runner.h" |
| #include "build/build_config.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/permission_manager.h" |
| #include "content/public/browser/permission_type.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_process_host_observer.h" |
| #include "content/public/browser/web_contents.h" |
| #include "media/base/cdm_config.h" |
| #include "media/base/cdm_factory.h" |
| #include "media/base/cdm_promise.h" |
| #include "media/base/limits.h" |
| |
| #if defined(OS_ANDROID) |
| #include "content/public/browser/android/provision_fetcher_factory.h" |
| #include "content/public/common/renderer_preferences.h" |
| #include "media/base/android/android_cdm_factory.h" |
| #endif |
| |
| namespace content { |
| |
| using media::MediaKeys; |
| |
| namespace { |
| |
| #if defined(OS_ANDROID) |
| // Android only supports 128-bit key IDs. |
| const size_t kAndroidKeyIdBytes = 128 / 8; |
| #endif |
| |
| // The ID used in this class is a concatenation of |render_frame_id| and |
| // |cdm_id|, i.e. (render_frame_id << 32) + cdm_id. |
| |
| uint64_t GetId(int render_frame_id, int cdm_id) { |
| return (static_cast<uint64_t>(render_frame_id) << 32) + |
| static_cast<uint64_t>(cdm_id); |
| } |
| |
| bool IdBelongsToFrame(uint64_t id, int render_frame_id) { |
| return (id >> 32) == static_cast<uint64_t>(render_frame_id); |
| } |
| |
| // media::CdmPromiseTemplate implementation backed by a BrowserCdmManager. |
| template <typename... T> |
| class CdmPromiseInternal : public media::CdmPromiseTemplate<T...> { |
| public: |
| CdmPromiseInternal(const base::WeakPtr<BrowserCdmManager>& manager, |
| int render_frame_id, |
| int cdm_id, |
| uint32_t promise_id) |
| : manager_(manager), |
| render_frame_id_(render_frame_id), |
| cdm_id_(cdm_id), |
| promise_id_(promise_id) { |
| DCHECK(manager_); |
| } |
| |
| ~CdmPromiseInternal() final {} |
| |
| // CdmPromiseTemplate<> implementation. |
| void resolve(const T&... result) final; |
| |
| void reject(MediaKeys::Exception exception, |
| uint32_t system_code, |
| const std::string& error_message) final { |
| MarkPromiseSettled(); |
| if (manager_) { |
| manager_->RejectPromise(render_frame_id_, cdm_id_, promise_id_, exception, |
| system_code, error_message); |
| } |
| } |
| |
| private: |
| using media::CdmPromiseTemplate<T...>::MarkPromiseSettled; |
| |
| base::WeakPtr<BrowserCdmManager> const manager_; |
| const int render_frame_id_; |
| const int cdm_id_; |
| const uint32_t promise_id_; |
| }; |
| |
| template <> |
| void CdmPromiseInternal<>::resolve() { |
| MarkPromiseSettled(); |
| if (manager_) |
| manager_->ResolvePromise(render_frame_id_, cdm_id_, promise_id_); |
| } |
| |
| template <> |
| void CdmPromiseInternal<std::string>::resolve(const std::string& session_id) { |
| MarkPromiseSettled(); |
| if (manager_) { |
| manager_->ResolvePromiseWithSession(render_frame_id_, cdm_id_, promise_id_, |
| session_id); |
| } |
| } |
| |
| typedef CdmPromiseInternal<> SimplePromise; |
| typedef CdmPromiseInternal<std::string> NewSessionPromise; |
| |
| // Render process ID to BrowserCdmManager map. |
| typedef std::map<int, BrowserCdmManager*> BrowserCdmManagerMap; |
| base::LazyInstance<BrowserCdmManagerMap>::Leaky g_browser_cdm_manager_map = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| // Keeps the BrowserCdmManager alive, and in the global map, for as long as the |
| // RenderProcessHost is connected to the child process. This class is a |
| // self-owned observer. |
| class BrowserCdmManagerProcessWatcher : public RenderProcessHostObserver { |
| public: |
| BrowserCdmManagerProcessWatcher( |
| int render_process_id, |
| const scoped_refptr<BrowserCdmManager>& manager) |
| : browser_cdm_manager_(manager) { |
| RenderProcessHost::FromID(render_process_id)->AddObserver(this); |
| CHECK(g_browser_cdm_manager_map.Get() |
| .insert(std::make_pair(render_process_id, manager.get())) |
| .second); |
| } |
| |
| // RenderProcessHostObserver: |
| void RenderProcessExited(RenderProcessHost* host, |
| base::TerminationStatus /* status */, |
| int /* exit_code */) override { |
| RemoveHostObserverAndDestroy(host); |
| } |
| |
| void RenderProcessHostDestroyed(RenderProcessHost* host) override { |
| RemoveHostObserverAndDestroy(host); |
| } |
| |
| private: |
| void RemoveHostObserverAndDestroy(RenderProcessHost* host) { |
| CHECK(g_browser_cdm_manager_map.Get().erase(host->GetID())); |
| host->RemoveObserver(this); |
| delete this; |
| } |
| |
| const scoped_refptr<BrowserCdmManager> browser_cdm_manager_; |
| }; |
| |
| } // namespace |
| |
| // static |
| BrowserCdmManager* BrowserCdmManager::FromProcess(int render_process_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| auto& map = g_browser_cdm_manager_map.Get(); |
| auto iterator = map.find(render_process_id); |
| return (iterator == map.end()) ? nullptr : iterator->second; |
| } |
| |
| BrowserCdmManager::BrowserCdmManager( |
| int render_process_id, |
| const scoped_refptr<base::TaskRunner>& task_runner) |
| : BrowserMessageFilter(CdmMsgStart), |
| render_process_id_(render_process_id), |
| task_runner_(task_runner), |
| weak_ptr_factory_(this) { |
| DVLOG(1) << __FUNCTION__ << ": " << render_process_id_; |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| new BrowserCdmManagerProcessWatcher(render_process_id, this); |
| |
| if (!task_runner_.get()) { |
| task_runner_ = |
| BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI); |
| } |
| } |
| |
| BrowserCdmManager::~BrowserCdmManager() { |
| DVLOG(1) << __FUNCTION__ << ": " << render_process_id_; |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| } |
| |
| // Makes sure BrowserCdmManager is always deleted on the Browser UI thread. |
| void BrowserCdmManager::OnDestruct() const { |
| DVLOG(1) << __FUNCTION__ << ": " << render_process_id_; |
| if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
| delete this; |
| } else { |
| BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this); |
| } |
| } |
| |
| base::TaskRunner* BrowserCdmManager::OverrideTaskRunnerForMessage( |
| const IPC::Message& message) { |
| // Only handles CDM messages. |
| if (IPC_MESSAGE_CLASS(message) != CdmMsgStart) |
| return NULL; |
| |
| return task_runner_.get(); |
| } |
| |
| bool BrowserCdmManager::OnMessageReceived(const IPC::Message& msg) { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(BrowserCdmManager, msg) |
| IPC_MESSAGE_HANDLER(CdmHostMsg_InitializeCdm, OnInitializeCdm) |
| IPC_MESSAGE_HANDLER(CdmHostMsg_SetServerCertificate, OnSetServerCertificate) |
| IPC_MESSAGE_HANDLER(CdmHostMsg_CreateSessionAndGenerateRequest, |
| OnCreateSessionAndGenerateRequest) |
| IPC_MESSAGE_HANDLER(CdmHostMsg_LoadSession, OnLoadSession) |
| IPC_MESSAGE_HANDLER(CdmHostMsg_UpdateSession, OnUpdateSession) |
| IPC_MESSAGE_HANDLER(CdmHostMsg_CloseSession, OnCloseSession) |
| IPC_MESSAGE_HANDLER(CdmHostMsg_RemoveSession, OnRemoveSession) |
| IPC_MESSAGE_HANDLER(CdmHostMsg_DestroyCdm, OnDestroyCdm) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| return handled; |
| } |
| |
| scoped_refptr<MediaKeys> BrowserCdmManager::GetCdm(int render_frame_id, |
| int cdm_id) const { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| const auto& iter = cdm_map_.find(GetId(render_frame_id, cdm_id)); |
| return iter == cdm_map_.end() ? nullptr : iter->second; |
| } |
| |
| void BrowserCdmManager::RenderFrameDeleted(int render_frame_id) { |
| if (!task_runner_->RunsTasksOnCurrentThread()) { |
| task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&BrowserCdmManager::RemoveAllCdmForFrame, |
| this, render_frame_id)); |
| return; |
| } |
| RemoveAllCdmForFrame(render_frame_id); |
| } |
| |
| void BrowserCdmManager::ResolvePromise(int render_frame_id, |
| int cdm_id, |
| uint32_t promise_id) { |
| Send(new CdmMsg_ResolvePromise(render_frame_id, cdm_id, promise_id)); |
| } |
| |
| void BrowserCdmManager::ResolvePromiseWithSession( |
| int render_frame_id, |
| int cdm_id, |
| uint32_t promise_id, |
| const std::string& session_id) { |
| if (session_id.length() > media::limits::kMaxSessionIdLength) { |
| RejectPromise(render_frame_id, cdm_id, promise_id, |
| MediaKeys::INVALID_ACCESS_ERROR, 0, |
| "Session ID is too long."); |
| return; |
| } |
| |
| Send(new CdmMsg_ResolvePromiseWithSession(render_frame_id, cdm_id, promise_id, |
| session_id)); |
| } |
| |
| void BrowserCdmManager::RejectPromise(int render_frame_id, |
| int cdm_id, |
| uint32_t promise_id, |
| MediaKeys::Exception exception, |
| uint32_t system_code, |
| const std::string& error_message) { |
| Send(new CdmMsg_RejectPromise(render_frame_id, cdm_id, promise_id, exception, |
| system_code, error_message)); |
| } |
| |
| media::CdmFactory* BrowserCdmManager::GetCdmFactory() { |
| if (!cdm_factory_) { |
| // Create a new CdmFactory. |
| cdm_factory_ = GetContentClient()->browser()->CreateCdmFactory(); |
| |
| #if defined(OS_ANDROID) |
| if (!cdm_factory_) { |
| // Obtain http request context for the current render process. |
| net::URLRequestContextGetter* context_getter = |
| RenderProcessHost::FromID(render_process_id_) |
| ->GetBrowserContext() |
| ->GetRequestContext(); |
| DCHECK(context_getter); |
| |
| cdm_factory_.reset(new media::AndroidCdmFactory( |
| base::Bind(&CreateProvisionFetcher, context_getter))); |
| } |
| #endif |
| } |
| |
| return cdm_factory_.get(); |
| } |
| |
| void BrowserCdmManager::OnSessionMessage(int render_frame_id, |
| int cdm_id, |
| const std::string& session_id, |
| MediaKeys::MessageType message_type, |
| const std::vector<uint8_t>& message, |
| const GURL& legacy_destination_url) { |
| GURL verified_gurl = legacy_destination_url; |
| if (!verified_gurl.is_valid() && !verified_gurl.is_empty()) { |
| DLOG(WARNING) << "SessionMessage legacy_destination_url is invalid : " |
| << legacy_destination_url.possibly_invalid_spec(); |
| verified_gurl = |
| GURL::EmptyGURL(); // Replace invalid legacy_destination_url. |
| } |
| |
| Send(new CdmMsg_SessionMessage(render_frame_id, cdm_id, session_id, |
| message_type, message, verified_gurl)); |
| } |
| |
| void BrowserCdmManager::OnSessionClosed(int render_frame_id, |
| int cdm_id, |
| const std::string& session_id) { |
| Send(new CdmMsg_SessionClosed(render_frame_id, cdm_id, session_id)); |
| } |
| |
| void BrowserCdmManager::OnLegacySessionError( |
| int render_frame_id, |
| int cdm_id, |
| const std::string& session_id, |
| MediaKeys::Exception exception_code, |
| uint32_t system_code, |
| const std::string& error_message) { |
| Send(new CdmMsg_LegacySessionError(render_frame_id, cdm_id, session_id, |
| exception_code, system_code, |
| error_message)); |
| } |
| |
| void BrowserCdmManager::OnSessionKeysChange(int render_frame_id, |
| int cdm_id, |
| const std::string& session_id, |
| bool has_additional_usable_key, |
| media::CdmKeysInfo keys_info) { |
| std::vector<media::CdmKeyInformation> key_info_vector; |
| for (const auto& key_info : keys_info) |
| key_info_vector.push_back(*key_info); |
| Send(new CdmMsg_SessionKeysChange(render_frame_id, cdm_id, session_id, |
| has_additional_usable_key, |
| key_info_vector)); |
| } |
| |
| void BrowserCdmManager::OnSessionExpirationUpdate( |
| int render_frame_id, |
| int cdm_id, |
| const std::string& session_id, |
| const base::Time& new_expiry_time) { |
| Send(new CdmMsg_SessionExpirationUpdate(render_frame_id, cdm_id, session_id, |
| new_expiry_time)); |
| } |
| |
| // Use a weak pointer here instead of |this| to avoid circular references. |
| #define BROWSER_CDM_MANAGER_CB(func, ...) \ |
| base::Bind(&BrowserCdmManager::func, weak_ptr_factory_.GetWeakPtr(), \ |
| render_frame_id, cdm_id, ##__VA_ARGS__) |
| |
| void BrowserCdmManager::OnInitializeCdm( |
| int render_frame_id, |
| int cdm_id, |
| uint32_t promise_id, |
| const CdmHostMsg_InitializeCdm_Params& params) { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| DCHECK(!GetCdm(render_frame_id, cdm_id)); |
| |
| scoped_ptr<SimplePromise> promise(new SimplePromise( |
| weak_ptr_factory_.GetWeakPtr(), render_frame_id, cdm_id, promise_id)); |
| |
| if (params.key_system.size() > media::limits::kMaxKeySystemLength) { |
| NOTREACHED() << "Invalid key system: " << params.key_system; |
| promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, "Invalid key system."); |
| return; |
| } |
| |
| if (!GetCdmFactory()) { |
| NOTREACHED() << "CDM not supported."; |
| promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, "CDM not supported."); |
| return; |
| } |
| |
| // The render process makes sure |allow_distinctive_identifier| and |
| // |allow_persistent_state| are true. See RenderCdmFactory::Create(). |
| // TODO(xhwang): Pass |allow_distinctive_identifier| and |
| // |allow_persistent_state| from the render process. |
| media::CdmConfig cdm_config; |
| cdm_config.allow_distinctive_identifier = true; |
| cdm_config.allow_persistent_state = true; |
| cdm_config.use_hw_secure_codecs = params.use_hw_secure_codecs; |
| |
| GetCdmFactory()->Create( |
| params.key_system, params.security_origin, cdm_config, |
| BROWSER_CDM_MANAGER_CB(OnSessionMessage), |
| BROWSER_CDM_MANAGER_CB(OnSessionClosed), |
| BROWSER_CDM_MANAGER_CB(OnLegacySessionError), |
| BROWSER_CDM_MANAGER_CB(OnSessionKeysChange), |
| BROWSER_CDM_MANAGER_CB(OnSessionExpirationUpdate), |
| BROWSER_CDM_MANAGER_CB(OnCdmCreated, params.security_origin, |
| base::Passed(&promise))); |
| } |
| |
| void BrowserCdmManager::OnSetServerCertificate( |
| int render_frame_id, |
| int cdm_id, |
| uint32_t promise_id, |
| const std::vector<uint8_t>& certificate) { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| |
| scoped_ptr<SimplePromise> promise(new SimplePromise( |
| weak_ptr_factory_.GetWeakPtr(), render_frame_id, cdm_id, promise_id)); |
| |
| scoped_refptr<MediaKeys> cdm = GetCdm(render_frame_id, cdm_id); |
| if (!cdm) { |
| promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, "CDM not found."); |
| return; |
| } |
| |
| if (certificate.empty()) { |
| promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, "Empty certificate."); |
| return; |
| } |
| |
| cdm->SetServerCertificate(certificate, std::move(promise)); |
| } |
| |
| void BrowserCdmManager::OnCreateSessionAndGenerateRequest( |
| const CdmHostMsg_CreateSessionAndGenerateRequest_Params& params) { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| |
| int render_frame_id = params.render_frame_id; |
| int cdm_id = params.cdm_id; |
| const std::vector<uint8_t>& init_data = params.init_data; |
| scoped_ptr<NewSessionPromise> promise( |
| new NewSessionPromise(weak_ptr_factory_.GetWeakPtr(), |
| render_frame_id, cdm_id, params.promise_id)); |
| |
| if (init_data.size() > media::limits::kMaxInitDataLength) { |
| LOG(WARNING) << "InitData for ID: " << cdm_id |
| << " too long: " << init_data.size(); |
| promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, "Init data too long."); |
| return; |
| } |
| #if defined(OS_ANDROID) |
| // 'webm' initData is a single key ID. On Android the length is restricted. |
| if (params.init_data_type == INIT_DATA_TYPE_WEBM && |
| init_data.size() != kAndroidKeyIdBytes) { |
| promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, |
| "'webm' initData is not the correct length."); |
| return; |
| } |
| #endif |
| |
| media::EmeInitDataType eme_init_data_type; |
| switch (params.init_data_type) { |
| case INIT_DATA_TYPE_WEBM: |
| eme_init_data_type = media::EmeInitDataType::WEBM; |
| break; |
| #if defined(USE_PROPRIETARY_CODECS) |
| case INIT_DATA_TYPE_CENC: |
| eme_init_data_type = media::EmeInitDataType::CENC; |
| break; |
| #endif |
| default: |
| NOTREACHED(); |
| promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, |
| "Invalid init data type."); |
| return; |
| } |
| |
| scoped_refptr<MediaKeys> cdm = GetCdm(render_frame_id, cdm_id); |
| if (!cdm) { |
| DLOG(WARNING) << "No CDM found for: " << render_frame_id << ", " << cdm_id; |
| promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, "CDM not found."); |
| return; |
| } |
| |
| CheckPermissionStatus( |
| render_frame_id, cdm_id, |
| base::Bind(&BrowserCdmManager::CreateSessionAndGenerateRequestIfPermitted, |
| this, render_frame_id, cdm_id, params.session_type, |
| eme_init_data_type, init_data, base::Passed(&promise))); |
| } |
| |
| void BrowserCdmManager::OnLoadSession( |
| int render_frame_id, |
| int cdm_id, |
| uint32_t promise_id, |
| media::MediaKeys::SessionType session_type, |
| const std::string& session_id) { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| |
| scoped_ptr<NewSessionPromise> promise(new NewSessionPromise( |
| weak_ptr_factory_.GetWeakPtr(), render_frame_id, cdm_id, promise_id)); |
| |
| scoped_refptr<MediaKeys> cdm = GetCdm(render_frame_id, cdm_id); |
| if (!cdm) { |
| DLOG(WARNING) << "No CDM found for: " << render_frame_id << ", " << cdm_id; |
| promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, "CDM not found."); |
| return; |
| } |
| |
| CheckPermissionStatus( |
| render_frame_id, cdm_id, |
| base::Bind(&BrowserCdmManager::LoadSessionIfPermitted, |
| this, render_frame_id, cdm_id, session_type, |
| session_id, base::Passed(&promise))); |
| } |
| |
| void BrowserCdmManager::OnUpdateSession(int render_frame_id, |
| int cdm_id, |
| uint32_t promise_id, |
| const std::string& session_id, |
| const std::vector<uint8_t>& response) { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| |
| scoped_ptr<SimplePromise> promise(new SimplePromise( |
| weak_ptr_factory_.GetWeakPtr(), render_frame_id, cdm_id, promise_id)); |
| |
| scoped_refptr<MediaKeys> cdm = GetCdm(render_frame_id, cdm_id); |
| if (!cdm) { |
| promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, "CDM not found."); |
| return; |
| } |
| |
| if (response.size() > media::limits::kMaxSessionResponseLength) { |
| LOG(WARNING) << "Response for ID " << cdm_id |
| << " is too long: " << response.size(); |
| promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, "Response too long."); |
| return; |
| } |
| |
| if (response.empty()) { |
| promise->reject(MediaKeys::INVALID_ACCESS_ERROR, 0, "Response is empty."); |
| return; |
| } |
| |
| cdm->UpdateSession(session_id, response, std::move(promise)); |
| } |
| |
| void BrowserCdmManager::OnCloseSession(int render_frame_id, |
| int cdm_id, |
| uint32_t promise_id, |
| const std::string& session_id) { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| |
| scoped_ptr<SimplePromise> promise(new SimplePromise( |
| weak_ptr_factory_.GetWeakPtr(), render_frame_id, cdm_id, promise_id)); |
| |
| scoped_refptr<MediaKeys> cdm = GetCdm(render_frame_id, cdm_id); |
| if (!cdm) { |
| promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, "CDM not found."); |
| return; |
| } |
| |
| cdm->CloseSession(session_id, std::move(promise)); |
| } |
| |
| void BrowserCdmManager::OnRemoveSession(int render_frame_id, |
| int cdm_id, |
| uint32_t promise_id, |
| const std::string& session_id) { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| |
| scoped_ptr<SimplePromise> promise(new SimplePromise( |
| weak_ptr_factory_.GetWeakPtr(), render_frame_id, cdm_id, promise_id)); |
| |
| scoped_refptr<MediaKeys> cdm = GetCdm(render_frame_id, cdm_id); |
| if (!cdm) { |
| promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, "CDM not found."); |
| return; |
| } |
| |
| cdm->RemoveSession(session_id, std::move(promise)); |
| } |
| |
| void BrowserCdmManager::OnDestroyCdm(int render_frame_id, int cdm_id) { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| RemoveCdm(GetId(render_frame_id, cdm_id)); |
| } |
| |
| void BrowserCdmManager::OnCdmCreated( |
| int render_frame_id, |
| int cdm_id, |
| const GURL& security_origin, |
| scoped_ptr<media::SimpleCdmPromise> promise, |
| const scoped_refptr<media::MediaKeys>& cdm, |
| const std::string& error_message) { |
| if (!cdm) { |
| DVLOG(1) << "Failed to create CDM: " << error_message; |
| promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, error_message); |
| return; |
| } |
| |
| uint64_t id = GetId(render_frame_id, cdm_id); |
| cdm_map_[id] = cdm; |
| cdm_security_origin_map_[id] = security_origin; |
| promise->resolve(); |
| } |
| |
| void BrowserCdmManager::RemoveAllCdmForFrame(int render_frame_id) { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| |
| std::vector<uint64_t> ids_to_remove; |
| for (const auto& entry : cdm_map_) { |
| if (IdBelongsToFrame(entry.first, render_frame_id)) |
| ids_to_remove.push_back(entry.first); |
| } |
| |
| for (const auto& id_to_remove : ids_to_remove) |
| RemoveCdm(id_to_remove); |
| } |
| |
| void BrowserCdmManager::RemoveCdm(uint64_t id) { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| |
| cdm_map_.erase(id); |
| cdm_security_origin_map_.erase(id); |
| } |
| |
| void BrowserCdmManager::CheckPermissionStatus( |
| int render_frame_id, |
| int cdm_id, |
| const PermissionStatusCB& permission_status_cb) { |
| // Always called on |task_runner_|, which may not be on the UI thread. |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| |
| GURL security_origin; |
| std::map<uint64_t, GURL>::const_iterator iter = |
| cdm_security_origin_map_.find(GetId(render_frame_id, cdm_id)); |
| DCHECK(iter != cdm_security_origin_map_.end()); |
| if (iter != cdm_security_origin_map_.end()) |
| security_origin = iter->second; |
| |
| if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&BrowserCdmManager::CheckPermissionStatusOnUIThread, this, |
| render_frame_id, security_origin, permission_status_cb)); |
| } else { |
| CheckPermissionStatusOnUIThread(render_frame_id, security_origin, |
| permission_status_cb); |
| } |
| } |
| |
| // Note: This function runs on the UI thread, which may be different from |
| // |task_runner_|. Be careful about thread safety! |
| void BrowserCdmManager::CheckPermissionStatusOnUIThread( |
| int render_frame_id, |
| const GURL& security_origin, |
| const base::Callback<void(bool)>& permission_status_cb) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| RenderFrameHost* rfh = |
| RenderFrameHost::FromID(render_process_id_, render_frame_id); |
| WebContents* web_contents = WebContents::FromRenderFrameHost(rfh); |
| PermissionManager* permission_manager = |
| web_contents->GetBrowserContext()->GetPermissionManager(); |
| if (!permission_manager) { |
| permission_status_cb.Run(false); |
| return; |
| } |
| |
| PermissionStatus permission_status = permission_manager->GetPermissionStatus( |
| PermissionType::PROTECTED_MEDIA_IDENTIFIER, security_origin, |
| web_contents->GetLastCommittedURL().GetOrigin()); |
| |
| bool allowed = (permission_status == PERMISSION_STATUS_GRANTED); |
| if (!task_runner_->RunsTasksOnCurrentThread()) { |
| task_runner_->PostTask(FROM_HERE, |
| base::Bind(permission_status_cb, allowed)); |
| } else { |
| permission_status_cb.Run(allowed); |
| } |
| } |
| |
| void BrowserCdmManager::CreateSessionAndGenerateRequestIfPermitted( |
| int render_frame_id, |
| int cdm_id, |
| media::MediaKeys::SessionType session_type, |
| media::EmeInitDataType init_data_type, |
| const std::vector<uint8_t>& init_data, |
| scoped_ptr<media::NewSessionCdmPromise> promise, |
| bool permission_was_allowed) { |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| |
| if (!permission_was_allowed) { |
| promise->reject(MediaKeys::NOT_SUPPORTED_ERROR, 0, "Permission denied."); |
| return; |
| } |
| |
| scoped_refptr<MediaKeys> cdm = GetCdm(render_frame_id, cdm_id); |
| if (!cdm) { |
| promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, "CDM not found."); |
| return; |
| } |
| |
| cdm->CreateSessionAndGenerateRequest(session_type, init_data_type, init_data, |
| std::move(promise)); |
| } |
| |
| void BrowserCdmManager::LoadSessionIfPermitted( |
| int render_frame_id, |
| int cdm_id, |
| media::MediaKeys::SessionType session_type, |
| const std::string& session_id, |
| scoped_ptr<media::NewSessionCdmPromise> promise, |
| bool permission_was_allowed) { |
| DCHECK_NE(media::MediaKeys::SessionType::TEMPORARY_SESSION, session_type); |
| DCHECK(task_runner_->RunsTasksOnCurrentThread()); |
| |
| if (!permission_was_allowed) { |
| promise->reject(MediaKeys::NOT_SUPPORTED_ERROR, 0, "Permission denied."); |
| return; |
| } |
| |
| scoped_refptr<MediaKeys> cdm = GetCdm(render_frame_id, cdm_id); |
| if (!cdm) { |
| promise->reject(MediaKeys::INVALID_STATE_ERROR, 0, "CDM not found."); |
| return; |
| } |
| |
| cdm->LoadSession(session_type, session_id, std::move(promise)); |
| } |
| |
| } // namespace content |