// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/browser/service_manager_context.h"
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/lazy_instance.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_pump_type.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/process/process_handle.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/deferred_sequenced_task_runner.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "chromecast/browser/cast_content_browser_client.h"
#include "chromecast/browser/service_manager_connection.h"
#include "chromecast/browser/system_connector.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/common/content_switches.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/invitation.h"
#include "sandbox/policy/mojom/sandbox.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/service_manager/public/cpp/constants.h"
#include "services/service_manager/public/cpp/manifest.h"
#include "services/service_manager/public/cpp/manifest_builder.h"
#include "services/service_manager/public/cpp/service.h"
#include "services/service_manager/public/mojom/service.mojom.h"
#include "services/service_manager/service_manager.h"
#include "services/service_manager/service_process_host.h"
#include "services/service_manager/service_process_launcher.h"
#include "ui/base/buildflags.h"
#include "ui/base/ui_base_features.h"
namespace chromecast {
namespace {
const char kSystemServiceName[] = "content_system";
base::LazyInstance<std::unique_ptr<service_manager::Connector>>::Leaky
g_io_thread_connector = LAZY_INSTANCE_INITIALIZER;
const service_manager::Manifest& GetBrowserManifest() {
static base::NoDestructor<service_manager::Manifest> manifest{
service_manager::ManifestBuilder()
.WithServiceName(ServiceManagerContext::kBrowserServiceName)
.WithDisplayName("Browser process")
.WithOptions(service_manager::ManifestOptionsBuilder()
.CanConnectToInstancesInAnyGroup(true)
.CanConnectToInstancesWithAnyId(true)
.CanRegisterOtherServiceInstances(true)
.Build())
.RequireCapability("*", "app")
.RequireCapability("*", "multizone")
.RequireCapability("*", "reconnect")
.RequireCapability("*", "renderer")
.Build()};
return *manifest;
}
service_manager::Manifest GetSystemManifest(
shell::CastContentBrowserClient* cast_content_browser_client) {
// TODO(crbug.com/40626947): This is a bit of a temporary hack so that
// we can make the global service instance a singleton. For now we just mirror
// the per-BrowserContext manifest (formerly also used for the global
// singleton instance), sans packaged services, since those are only meant to
// be tied to a BrowserContext. The per-BrowserContext service should go away
// soon, and then this can be removed.
service_manager::Manifest manifest = GetBrowserManifest();
manifest.Amend(cast_content_browser_client
->GetServiceManifestOverlay(
ServiceManagerContext::kBrowserServiceName)
.value_or(service_manager::Manifest()));
manifest.service_name = kSystemServiceName;
manifest.packaged_services.clear();
manifest.options.instance_sharing_policy =
service_manager::Manifest::InstanceSharingPolicy::kSingleton;
return manifest;
}
void DestroyConnectorOnIOThread() {
g_io_thread_connector.Get().reset();
}
// A ServiceProcessHost implementation which uses the Service Manager's builtin
// service executable launcher. Not yet intended for use in production Chrome,
// hence availability is gated behind a flag.
class ServiceExecutableProcessHost
: public service_manager::ServiceProcessHost {
public:
explicit ServiceExecutableProcessHost(const base::FilePath& executable_path)
: launcher_(nullptr, executable_path) {}
ServiceExecutableProcessHost(const ServiceExecutableProcessHost&) = delete;
ServiceExecutableProcessHost& operator=(const ServiceExecutableProcessHost&) =
delete;
~ServiceExecutableProcessHost() override = default;
// service_manager::ServiceProcessHost:
mojo::PendingRemote<service_manager::mojom::Service> Launch(
const service_manager::Identity& identity,
sandbox::mojom::Sandbox sandbox_type,
const std::u16string& display_name,
LaunchCallback callback) override {
// TODO(crbug.com/41353434): Support sandboxing.
return launcher_.Start(identity, sandbox::mojom::Sandbox::kNoSandbox,
std::move(callback));
}
private:
service_manager::ServiceProcessLauncher launcher_;
};
using ServiceRequestHandler = base::RepeatingCallback<void(
const service_manager::Identity& identity,
mojo::PendingReceiver<service_manager::mojom::Service> receiver)>;
// Implements in- and out-of-process service instance launching for services
// built into the Content embedder's binary.
//
// All methods on this object (except the constructor) are called on the Service
// Manager's thread, which is effectively the browser's IO thread.
class BrowserServiceManagerDelegate
: public service_manager::ServiceManager::Delegate {
public:
BrowserServiceManagerDelegate(
const scoped_refptr<base::SequencedTaskRunner>& main_thread_task_runner,
ServiceRequestHandler main_thread_request_handler)
: main_thread_task_runner_(main_thread_task_runner),
main_thread_request_handler_(std::move(main_thread_request_handler)) {}
BrowserServiceManagerDelegate(const BrowserServiceManagerDelegate&) = delete;
BrowserServiceManagerDelegate& operator=(
const BrowserServiceManagerDelegate&) = delete;
~BrowserServiceManagerDelegate() override = default;
// service_manager::ServiceManager::Delegate:
bool RunBuiltinServiceInstanceInCurrentProcess(
const service_manager::Identity& identity,
mojo::PendingReceiver<service_manager::mojom::Service> receiver)
override {
main_thread_task_runner_->PostTask(
FROM_HERE, base::BindOnce(main_thread_request_handler_, identity,
std::move(receiver)));
return true;
}
std::unique_ptr<service_manager::ServiceProcessHost>
CreateProcessHostForBuiltinServiceInstance(
const service_manager::Identity& identity) override {
// Cast only uses the default kInProcessBuiltin mode. This function should
// only be called for kOutOfProcessBuiltin mode.
NOTREACHED();
}
std::unique_ptr<service_manager::ServiceProcessHost>
CreateProcessHostForServiceExecutable(
const base::FilePath& executable_path) override {
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableServiceBinaryLauncher)) {
return nullptr;
}
return std::make_unique<ServiceExecutableProcessHost>(executable_path);
}
private:
const scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner_;
const ServiceRequestHandler main_thread_request_handler_;
};
} // namespace
const char ServiceManagerContext::kBrowserServiceName[] = "content_browser";
// State which lives on the IO thread and drives the ServiceManager.
class ServiceManagerContext::InProcessServiceManagerContext
: public base::RefCountedThreadSafe<InProcessServiceManagerContext> {
public:
InProcessServiceManagerContext(scoped_refptr<base::SingleThreadTaskRunner>
service_manager_thread_task_runner)
: service_manager_thread_task_runner_(
service_manager_thread_task_runner) {}
InProcessServiceManagerContext(const InProcessServiceManagerContext&) =
delete;
InProcessServiceManagerContext& operator=(
const InProcessServiceManagerContext&) = delete;
void Start(std::vector<service_manager::Manifest> manifests,
mojo::PendingRemote<service_manager::mojom::Service> system_remote,
ServiceRequestHandler request_handler) {
service_manager_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&InProcessServiceManagerContext::StartOnServiceManagerThread, this,
std::move(manifests),
base::SingleThreadTaskRunner::GetCurrentDefault(),
std::move(system_remote), std::move(request_handler)));
}
void ShutDown() {
service_manager_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&InProcessServiceManagerContext::ShutDownOnServiceManagerThread,
this));
}
void StartServices(std::vector<std::string> service_names) {
service_manager_thread_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&InProcessServiceManagerContext ::
StartServicesOnServiceManagerThread,
this, std::move(service_names)));
}
private:
friend class base::RefCountedThreadSafe<InProcessServiceManagerContext>;
~InProcessServiceManagerContext() = default;
void StartOnServiceManagerThread(
std::vector<service_manager::Manifest> manifests,
scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner,
mojo::PendingRemote<service_manager::mojom::Service> system_remote,
ServiceRequestHandler request_handler) {
service_manager_ = std::make_unique<service_manager::ServiceManager>(
std::move(manifests),
std::make_unique<BrowserServiceManagerDelegate>(
ui_thread_task_runner, std::move(request_handler)));
mojo::Remote<service_manager::mojom::ProcessMetadata> metadata;
service_manager_->RegisterService(
service_manager::Identity(kSystemServiceName,
service_manager::kSystemInstanceGroup,
base::Token{}, base::Token::CreateRandom()),
std::move(system_remote), metadata.BindNewPipeAndPassReceiver());
metadata->SetPID(base::GetCurrentProcId());
}
void ShutDownOnServiceManagerThread() { service_manager_.reset(); }
void StartServicesOnServiceManagerThread(
std::vector<std::string> service_names) {
if (!service_manager_)
return;
for (const auto& service_name : service_names)
service_manager_->StartService(service_name);
}
const scoped_refptr<base::SingleThreadTaskRunner>
service_manager_thread_task_runner_;
std::unique_ptr<service_manager::ServiceManager> service_manager_;
};
ServiceManagerContext::ServiceManagerContext(
shell::CastContentBrowserClient* cast_content_browser_client,
scoped_refptr<base::SingleThreadTaskRunner>
service_manager_thread_task_runner)
: cast_content_browser_client_(cast_content_browser_client),
service_manager_thread_task_runner_(
std::move(service_manager_thread_task_runner)) {
// The |service_manager_thread_task_runner_| must have been created before
// starting the ServiceManager.
DCHECK(service_manager_thread_task_runner_);
std::vector<service_manager::Manifest> manifests;
manifests.push_back(GetBrowserManifest());
manifests.push_back(GetSystemManifest(cast_content_browser_client_));
for (auto& manifest : manifests) {
std::optional<service_manager::Manifest> overlay =
cast_content_browser_client_->GetServiceManifestOverlay(
manifest.service_name);
if (overlay)
manifest.Amend(*overlay);
}
for (auto& extra_manifest :
cast_content_browser_client_->GetExtraServiceManifests()) {
manifests.emplace_back(std::move(extra_manifest));
}
in_process_context_ =
new InProcessServiceManagerContext(service_manager_thread_task_runner_);
mojo::PendingRemote<service_manager::mojom::Service> system_remote;
ServiceManagerConnection::SetForProcess(ServiceManagerConnection::Create(
system_remote.InitWithNewPipeAndPassReceiver(),
service_manager_thread_task_runner_));
auto* system_connection = ServiceManagerConnection::GetForProcess();
SetSystemConnector(system_connection->GetConnector()->Clone());
// This is safe to assign directly from any thread, because
// ServiceManagerContext must be constructed before anyone can call
// GetConnectorForIOThread().
g_io_thread_connector.Get() = system_connection->GetConnector()->Clone();
in_process_context_->Start(
manifests, std::move(system_remote),
base::BindRepeating(&ServiceManagerContext::RunServiceInstance,
weak_ptr_factory_.GetWeakPtr()));
in_process_context_->StartServices(
cast_content_browser_client_->GetStartupServices());
}
ServiceManagerContext::~ServiceManagerContext() {
ShutDown();
}
void ServiceManagerContext::ShutDown() {
// NOTE: The in-process ServiceManager MUST be destroyed before the browser
// process-wide ServiceManagerConnection. Otherwise it's possible for the
// ServiceManager to receive connection requests for service:content_browser
// which it may attempt to service by launching a new instance of the browser.
if (in_process_context_)
in_process_context_->ShutDown();
if (ServiceManagerConnection::GetForProcess())
ServiceManagerConnection::DestroyForProcess();
service_manager_thread_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&DestroyConnectorOnIOThread));
}
// static
service_manager::Connector* ServiceManagerContext::GetConnectorForIOThread() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
return g_io_thread_connector.Get().get();
}
void ServiceManagerContext::RunServiceInstance(
const service_manager::Identity& identity,
mojo::PendingReceiver<service_manager::mojom::Service> receiver) {
cast_content_browser_client_->RunServiceInstance(identity, &receiver);
DLOG_IF(ERROR, receiver) << "Unhandled service request for \""
<< identity.name() << "\"";
}
} // namespace chromecast