blob: de456f16f600b84121bf9a77e49ea96bccd822da [file] [log] [blame]
// Copyright 2021 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/service_factory.h"
#include <map>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/threading/sequence_local_storage_slot.h"
#include "base/time/time.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/media_buildflags.h"
#include "media/mojo/mojom/cdm_service.mojom.h"
#if defined(OS_MAC)
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "sandbox/mac/seatbelt_extension.h"
#endif // defined(OS_MAC)
#if defined(OS_WIN)
#include "media/mojo/mojom/media_foundation_service.mojom.h"
#endif // defined(OS_WIN)
namespace content {
namespace {
#if defined(OS_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 // defined(OS_MAC)
// 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<base::Token, 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 <>
struct ServiceTraits<media::mojom::CdmService> {
using BrokerType = media::mojom::CdmServiceBroker;
};
#if defined(OS_WIN)
template <>
struct ServiceTraits<media::mojom::MediaFoundationService> {
using BrokerType = media::mojom::MediaFoundationServiceBroker;
};
#endif // defined(OS_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 `guid`, `browser_context` and `site`.
// Instances are started lazily as needed.
template <typename T>
T& GetService(const base::Token& guid,
BrowserContext* browser_context,
const GURL& site,
const std::string& service_name,
const base::FilePath& cdm_path) {
ServiceKey key;
std::string display_name = service_name;
if (base::FeatureList::IsEnabled(media::kCdmProcessSiteIsolation)) {
key = {guid, browser_context, site};
auto site_display_name =
GetContentClient()->browser()->GetSiteDisplayNameForCdmProcess(
browser_context, site);
if (!site_display_name.empty())
display_name += " (" + site_display_name + ")";
} else {
key = {guid, nullptr, GURL()};
}
DVLOG(2) << __func__ << ": key=" << key;
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);
ServiceProcessHost::Launch(broker_remote.BindNewPipeAndPassReceiver(),
options.Pass());
#if defined(OS_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 // defined(OS_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(const base::Token& guid,
BrowserContext* browser_context,
const GURL& site,
const CdmInfo& cdm_info) {
return GetService<media::mojom::CdmService>(guid, browser_context, site,
cdm_info.name, cdm_info.path);
}
#if defined(OS_WIN)
media::mojom::MediaFoundationService& GetMediaFoundationService(
BrowserContext* browser_context,
const GURL& site,
const base::FilePath& cdm_path) {
return GetService<media::mojom::MediaFoundationService>(
base::Token(), browser_context, site, "Media Foundation Service",
cdm_path);
}
#endif // defined(OS_WIN)
} // namespace content