chromium/chromecast/browser/service_manager_context.cc

// 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