blob: fd6740d95a0395d3c86f007f2ad2505a067336c5 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/media/service_factory.h"
#include <map>
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/threading/sequence_local_storage_slot.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/common/content_client.h"
#include "media/base/cdm_context.h"
#include "media/base/media_switches.h"
#include "media/cdm/cdm_type.h"
#include "media/media_buildflags.h"
#include "media/mojo/mojom/cdm_service.mojom.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#if BUILDFLAG(IS_MAC)
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "sandbox/mac/seatbelt_extension.h"
#endif // BUILDFLAG(IS_MAC)
#if BUILDFLAG(IS_WIN)
#include "content/public/browser/gpu_data_manager.h"
#include "content/public/browser/gpu_data_manager_observer.h"
#include "media/mojo/mojom/media_foundation_service.mojom.h"
#endif // BUILDFLAG(IS_WIN)
namespace content {
namespace {
#if BUILDFLAG(IS_MAC)
#if BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION)
// TODO(xhwang): Move this to a common place.
const base::FilePath::CharType kSignatureFileExtension[] =
FILE_PATH_LITERAL(".sig");
// Returns the signature file path given the |file_path|. This function should
// only be used when the signature file and the file are located in the same
// directory, which is the case for the CDM and CDM adapter.
base::FilePath GetSigFilePath(const base::FilePath& file_path) {
return file_path.AddExtension(kSignatureFileExtension);
}
#endif // BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION)
class SeatbeltExtensionTokenProviderImpl final
: public media::mojom::SeatbeltExtensionTokenProvider {
public:
explicit SeatbeltExtensionTokenProviderImpl(const base::FilePath& cdm_path)
: cdm_path_(cdm_path) {}
SeatbeltExtensionTokenProviderImpl(
const SeatbeltExtensionTokenProviderImpl&) = delete;
SeatbeltExtensionTokenProviderImpl operator=(
const SeatbeltExtensionTokenProviderImpl&) = delete;
~SeatbeltExtensionTokenProviderImpl() override = default;
void GetTokens(GetTokensCallback callback) override {
DVLOG(1) << __func__;
std::vector<sandbox::SeatbeltExtensionToken> tokens;
// Allow the CDM to be loaded in the CDM service process.
auto cdm_token = sandbox::SeatbeltExtension::Issue(
sandbox::SeatbeltExtension::FILE_READ, cdm_path_.value());
if (cdm_token) {
tokens.push_back(std::move(*cdm_token));
} else {
std::move(callback).Run({});
return;
}
#if BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION)
// If CDM host verification is enabled, also allow to open the CDM signature
// file.
auto cdm_sig_token =
sandbox::SeatbeltExtension::Issue(sandbox::SeatbeltExtension::FILE_READ,
GetSigFilePath(cdm_path_).value());
if (cdm_sig_token) {
tokens.push_back(std::move(*cdm_sig_token));
} else {
std::move(callback).Run({});
return;
}
#endif // BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION)
std::move(callback).Run(std::move(tokens));
}
private:
base::FilePath cdm_path_;
};
#endif // BUILDFLAG(IS_MAC)
#if BUILDFLAG(IS_WIN)
// A singleton running in the browser process to notify (multiple) service
// processes on GpuInfo updates.
class GpuInfoMonitor : public GpuDataManagerObserver {
public:
static GpuInfoMonitor* GetInstance() {
static GpuInfoMonitor* instance = new GpuInfoMonitor();
return instance;
}
GpuInfoMonitor() { GpuDataManager::GetInstance()->AddObserver(this); }
void RegisterGpuInfoObserver(
mojo::PendingRemote<media::mojom::GpuInfoObserver> observer) {
auto observer_id = gpu_info_observers_.Add(std::move(observer));
// Notify upon registration in case there's a GPUInfo change between
// `InitializeBroker()` and when this observer is registered.
gpu_info_observers_.Get(observer_id)
->OnGpuInfoUpdate(GpuDataManager::GetInstance()->GetGPUInfo());
}
// GpuDataManagerObserver:
void OnGpuInfoUpdate() override {
for (const auto& observer : gpu_info_observers_) {
observer->OnGpuInfoUpdate(GpuDataManager::GetInstance()->GetGPUInfo());
}
}
private:
mojo::RemoteSet<media::mojom::GpuInfoObserver> gpu_info_observers_;
};
void RegisterGpuInfoObserver(
mojo::PendingRemote<media::mojom::GpuInfoObserver> observer) {
GpuInfoMonitor::GetInstance()->RegisterGpuInfoObserver(std::move(observer));
}
#endif // BUILDFLAG(IS_WIN)
// How long an instance of the service is allowed to sit idle before we
// disconnect and effectively kill it.
constexpr auto kServiceIdleTimeout = base::Seconds(5);
// Services are keyed on CDM type, user profile and site URL. Note that site
// is not normal URL nor origin. See chrome/browser/site_isolation for details.
using ServiceKey = std::tuple<media::CdmType, const BrowserContext*, GURL>;
std::ostream& operator<<(std::ostream& os, const ServiceKey& key) {
return os << "{" << std::get<0>(key).ToString() << ", " << std::get<1>(key)
<< ", " << std::get<2>(key) << "}";
}
template <typename T>
struct ServiceTraits {};
template <typename BrokerRemoteType>
void InitializeBroker(BrokerRemoteType& broker_remote) {}
template <>
struct ServiceTraits<media::mojom::CdmService> {
using BrokerType = media::mojom::CdmServiceBroker;
};
#if BUILDFLAG(IS_WIN)
template <>
struct ServiceTraits<media::mojom::MediaFoundationService> {
using BrokerType = media::mojom::MediaFoundationServiceBroker;
};
template <>
void InitializeBroker(
mojo::Remote<media::mojom::MediaFoundationServiceBroker>& broker_remote) {
broker_remote->UpdateGpuInfo(GpuDataManager::GetInstance()->GetGPUInfo(),
base::BindOnce(&RegisterGpuInfoObserver));
}
#endif // BUILDFLAG(IS_WIN)
// A map hosts all service remotes, each of which corresponds to one service
// process. There should be only one instance of this class stored in
// base::SequenceLocalStorageSlot. See below.
template <typename T>
class ServiceMap {
public:
ServiceMap() = default;
~ServiceMap() = default;
// Gets or creates a service remote. The returned remote might not be bound,
// e.g. if it's newly created.
auto& GetOrCreateRemote(const ServiceKey& key) { return remotes_[key]; }
void EraseRemote(const ServiceKey& key) {
DCHECK(remotes_.count(key));
remotes_.erase(key);
}
private:
using BrokerType = typename ServiceTraits<T>::BrokerType;
// Keep the broker remote to keep the process alive. Keep the service remote
// for reuse and for monitoring idle state (see below).
std::map<ServiceKey, std::pair<mojo::Remote<BrokerType>, mojo::Remote<T>>>
remotes_;
};
template <typename T>
ServiceMap<T>& GetServiceMap() {
// NOTE: Sequence-local storage is used to limit the lifetime of the Remote
// objects to that of the UI-thread sequence. This ensures the Remotes are
// destroyed when the task environment is torn down and reinitialized, e.g.,
// between unit tests.
static base::SequenceLocalStorageSlot<ServiceMap<T>> slot;
return slot.GetOrCreateValue();
}
// Erases the service instance identified by `key`.
template <typename T>
void EraseCdmService(const ServiceKey& key) {
DVLOG(2) << __func__ << ": key=" << key;
GetServiceMap<T>().EraseRemote(key);
}
// Gets an instance of the service for `cdm_type`, `browser_context` and `site`.
// Instances are started lazily as needed.
template <typename T>
T& GetService(const media::CdmType& cdm_type,
BrowserContext* browser_context,
const GURL& site,
const std::string& service_name,
const base::FilePath& cdm_path) {
// The service is always per CDM type, per user profile and per site.
ServiceKey key = {cdm_type, browser_context, site};
DVLOG(2) << __func__ << ": key=" << key;
// Generate the service display name.
std::string display_name = service_name;
auto site_display_name =
GetContentClient()->browser()->GetSiteDisplayNameForCdmProcess(
browser_context, site);
if (!site_display_name.empty()) {
display_name += " (" + site_display_name + ")";
}
auto& broker_service_pair = GetServiceMap<T>().GetOrCreateRemote(key);
auto& broker_remote = broker_service_pair.first;
auto& remote = broker_service_pair.second;
if (!remote) {
ServiceProcessHost::Options options;
options.WithDisplayName(display_name);
options.WithSite(site);
ServiceProcessHost::Launch(broker_remote.BindNewPipeAndPassReceiver(),
options.Pass());
// Initialize the broker if necessary.
InitializeBroker(broker_remote);
#if BUILDFLAG(IS_MAC)
mojo::PendingRemote<media::mojom::SeatbeltExtensionTokenProvider>
token_provider_remote;
mojo::MakeSelfOwnedReceiver(
std::make_unique<SeatbeltExtensionTokenProviderImpl>(cdm_path),
token_provider_remote.InitWithNewPipeAndPassReceiver());
broker_remote->GetService(cdm_path, std::move(token_provider_remote),
remote.BindNewPipeAndPassReceiver());
#else
broker_remote->GetService(cdm_path, remote.BindNewPipeAndPassReceiver());
#endif // BUILDFLAG(IS_MAC)
// The idle handler must be set on the `remote` because the `broker_remote`
// will never idle when the `remote` is bound.
remote.set_disconnect_handler(base::BindOnce(&EraseCdmService<T>, key));
remote.set_idle_handler(kServiceIdleTimeout,
base::BindRepeating(EraseCdmService<T>, key));
}
return *remote.get();
}
} // namespace
media::mojom::CdmService& GetCdmService(BrowserContext* browser_context,
const GURL& site,
const CdmInfo& cdm_info) {
return GetService<media::mojom::CdmService>(
cdm_info.type, browser_context, site, cdm_info.name, cdm_info.path);
}
#if BUILDFLAG(IS_WIN)
media::mojom::MediaFoundationService& GetMediaFoundationService(
const media::CdmType& cdm_type,
BrowserContext* browser_context,
const GURL& site,
const base::FilePath& cdm_path) {
return GetService<media::mojom::MediaFoundationService>(
cdm_type, browser_context, site, "Media Foundation Service", cdm_path);
}
#endif // BUILDFLAG(IS_WIN)
} // namespace content