Update service-related documentation
This wipes out obsolete references to Service Manager in service-related documentation and replaces it with simpler docs reflecting the new service model. Bug: None Change-Id: Iab5662bcc48b34954a9003532cd407058e470282 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2063773 Commit-Queue: Ken Rockot <rockot@google.com> Reviewed-by: Oksana Zhuravlova <oksamyt@chromium.org> Cr-Commit-Position: refs/heads/master@{#742677}
This commit is contained in:
@ -368,8 +368,6 @@ used when committed.
|
||||
to Mojo and services in Chromium, with examples
|
||||
* [Mojo API Reference](/mojo/README.md) - Detailed reference documentation for
|
||||
all things Mojo
|
||||
* [The Service Manager & Services](/services/service_manager/README.md) -
|
||||
Services system overview, API references, example services
|
||||
* [Service Development Guidelines](/services/README.md) - Guidelines for
|
||||
service development in the Chromium tree
|
||||
* [Servicifying Chromium Features](servicification.md) - General advice for
|
||||
|
@ -242,51 +242,23 @@ Content browser/renderer/gpu/utility process split.
|
||||
|
||||
A **service** is a self-contained library of code which implements one or more
|
||||
related features or behaviors and whose interaction with outside code is done
|
||||
*exclusively* through Mojo interface connections facilitated by the **Service
|
||||
Manager.**
|
||||
*exclusively* through Mojo interface connections, typically brokered by the
|
||||
browser process.
|
||||
|
||||
The **Service Manager** is a component which can run in a dedicated process
|
||||
or embedded within another process. Only one Service Manager exists globally
|
||||
across the system, and in Chromium the browser process runs an embedded Service
|
||||
Manager instance immediately on startup. The Service Manager spawns
|
||||
**service instances** on-demand, and it routes each interface request from a
|
||||
service instance to some destination instance of the Service Manager's choosing.
|
||||
|
||||
Each service instance implements the
|
||||
[**`Service`**](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/service.h)
|
||||
interface to receive incoming interface requests brokered by the Service
|
||||
Manager, and each service instance has a
|
||||
[**`Connector`**](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/connector.h)
|
||||
it can use to issue interface requests to other services via the
|
||||
Service Manager.
|
||||
|
||||
Every service has a **manifest** which declares some static metadata about the
|
||||
service. This metadata is used by the Service Manager for various purposes,
|
||||
including as a declaration of what interfaces are exposed to other services in
|
||||
the system. This eases the security review process.
|
||||
|
||||
Inside its manifest every service declares its **service name**, used to
|
||||
identify instances of the service in the most general sense. Names are free-form
|
||||
and usually short strings which must be globally unique. Some services defined
|
||||
in Chromium today include `"device"`, `"identity"`, and `"network"` services.
|
||||
|
||||
For more complete and in-depth coverage of the concepts covered here and other
|
||||
related APIs, see the
|
||||
[Service Manager documentation](/services/service_manager/README.md).
|
||||
Each service defines and implements a main Mojo interface which can be used
|
||||
by the browser to manage an instance of the service.
|
||||
|
||||
## Example: Building a Simple Out-of-Process Service
|
||||
|
||||
There are multiple steps required to get a new service up and running in
|
||||
Chromium. You must:
|
||||
There are multiple steps typically involved to get a new service up and running
|
||||
in Chromium:
|
||||
|
||||
- Define the `Service` implementation
|
||||
- Define the service's manifest
|
||||
- Tell Chromium's Service Manager about the manifest
|
||||
- Tell Chromium how to instantiate the `Service` implementation when it's needed
|
||||
- Define the main service interface and implementation
|
||||
- Hook up the implementation in out-of-process code
|
||||
- Write some browser logic to launch a service process
|
||||
|
||||
This section walks through these steps with some brief explanations. For more
|
||||
thorough documentation of the concepts and APIs used herein, see the
|
||||
[Service Manager](/services/service_manager/README.md) and
|
||||
[Mojo](/mojo/README.md) documentation.
|
||||
|
||||
### Defining the Service
|
||||
@ -299,20 +271,10 @@ define a new service for use by Chrome specifically, so we'll define it within
|
||||
We can create the following files. First some mojoms:
|
||||
|
||||
``` cpp
|
||||
// src/chrome/services/math/public/mojom/constants.mojom
|
||||
// src/chrome/services/math/public/mojom/math_service.mojom
|
||||
module math.mojom;
|
||||
|
||||
// These are not used by the implementation directly, but will be used in
|
||||
// following sections.
|
||||
const string kServiceName = "math";
|
||||
const string kArithmeticCapability = "arithmetic";
|
||||
```
|
||||
|
||||
``` cpp
|
||||
// src/chrome/services/math/public/mojom/divider.mojom
|
||||
module math.mojom;
|
||||
|
||||
interface Divider {
|
||||
interface MathService {
|
||||
Divide(int32 dividend, int32 divisor) => (int32 quotient);
|
||||
};
|
||||
```
|
||||
@ -323,46 +285,32 @@ import("//mojo/public/tools/bindings/mojom.gni")
|
||||
|
||||
mojom("mojom") {
|
||||
sources = [
|
||||
"constants.mojom",
|
||||
"divider.mojom",
|
||||
"math_service.mojom",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Then the actual `Service` implementation:
|
||||
Then the actual `MathService` implementation:
|
||||
|
||||
``` cpp
|
||||
// src/chrome/services/math/math_service.h
|
||||
#include "services/service_manager/public/cpp/service.h"
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "chrome/services/math/public/mojom/divider.mojom.h"
|
||||
#include "chrome/services/math/public/mojom/math_service.mojom.h"
|
||||
|
||||
namespace math {
|
||||
|
||||
class MathService : public service_manager::Service,
|
||||
public mojom::Divider {
|
||||
class MathService : public mojom::MathService {
|
||||
public:
|
||||
explicit MathService(service_manager::mojom::ServiceRequest request);
|
||||
explicit MathService(mojo::PendingReceiver<mojom::MathService> receiver);
|
||||
~MathService() override;
|
||||
|
||||
private:
|
||||
// service_manager::Service:
|
||||
void OnBindInterface(const service_manager::BindSourceInfo& source,
|
||||
const std::string& interface_name,
|
||||
mojo::ScopedMessagePipeHandle interface_pipe) override;
|
||||
|
||||
// mojom::Divider:
|
||||
// mojom::MathService:
|
||||
void Divide(int32_t dividend,
|
||||
int32_t divisor,
|
||||
DivideCallback callback) override;
|
||||
|
||||
service_manager::ServiceBinding service_binding_;
|
||||
|
||||
// You could also use a Receiver. We use ReceiverSet to conveniently allow
|
||||
// multiple clients to bind to the same instance of this class. See Mojo
|
||||
// C++ Bindings documentation for more information.
|
||||
mojo::ReceiverSet<mojom::Divider> divider_receivers_;
|
||||
mojo::Receiver<mojom::MathService> receiver_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MathService);
|
||||
};
|
||||
@ -376,23 +324,11 @@ class MathService : public service_manager::Service,
|
||||
|
||||
namespace math {
|
||||
|
||||
MathService::MathService(service_manager::ServiceRequest request)
|
||||
: service_binding_(this, std::move(request)) {}
|
||||
MathService::MathService(mojo::PendingReceiver<mojom::MathService> receiver)
|
||||
: receiver_(this, std::move(receiver)) {}
|
||||
|
||||
MathService::~MathService() = default;
|
||||
|
||||
void MathService::OnBindInterface(
|
||||
const service_manager::BindSourceInfo& source,
|
||||
const std::string& interface_name,
|
||||
mojo::ScopedMessagePipeHandle interface_pipe) {
|
||||
// Note that services typically use a service_manager::BinderRegistry if they
|
||||
// plan on handling many different interface request types.
|
||||
if (interface_name == mojom::Divider::Name_) {
|
||||
divider_receivers_.Add(
|
||||
this, mojo::PendingReceiver<mojom::Divider>(std::move(interface_pipe)));
|
||||
}
|
||||
}
|
||||
|
||||
void MathService::Divide(int32_t dividend,
|
||||
int32_t divisor,
|
||||
DivideCallback callback) {
|
||||
@ -415,321 +351,81 @@ source_set("math") {
|
||||
deps = [
|
||||
"//base",
|
||||
"//chrome/services/math/public/mojom",
|
||||
"//services/service_manager/public/cpp",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Now we have a fully defined `math` service implementation, including a nice
|
||||
little `Divider` interface for clients to play with. Next we need to define the
|
||||
service's manifest to declare how the service can be used.
|
||||
|
||||
### Defining the Manifest
|
||||
Manifests are defined as
|
||||
[`Manifest`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/manifest.h)
|
||||
objects, typically built using a
|
||||
[`ManifestBuilder`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/manifest_builder.h). As a general rule, services should define their manifest
|
||||
in a dedicated `source_set` or `component` target under their `public/cpp`
|
||||
subdirectory (typically referred to as the service's **C++ client library**).
|
||||
|
||||
We can create the following files for this purpose:
|
||||
|
||||
``` cpp
|
||||
// src/chrome/services/math/public/cpp/manifest.h
|
||||
#include "services/service_manager/public/cpp/manifest.h"
|
||||
|
||||
namespace math {
|
||||
|
||||
const service_manager::Manifest& GetManifest();
|
||||
|
||||
} // namespace math
|
||||
```
|
||||
|
||||
``` cpp
|
||||
// src/chrome/services/math/public/cpp/manifest.cc
|
||||
#include "chrome/services/math/public/cpp/manifest.h"
|
||||
|
||||
#include "base/no_destructor.h"
|
||||
#include "chrome/services/math/public/mojom/constants.mojom.h"
|
||||
#include "chrome/services/math/public/mojom/divider.mojom.h"
|
||||
#include "services/service_manager/public/cpp/manifest_builder.h"
|
||||
|
||||
namespace math {
|
||||
|
||||
const service_manager::Manifest& GetManifest() {
|
||||
static base::NoDestructor<service_manager::Manifest> manifest{
|
||||
service_manager::ManifestBuilder()
|
||||
.WithServiceName(mojom::kServiceName)
|
||||
.ExposeCapability(
|
||||
mojom::kArithmeticCapability,
|
||||
service_manager::Manifest::InterfaceList<mojom::Divider>())
|
||||
.Build()};
|
||||
return *manifest
|
||||
}
|
||||
|
||||
} // namespace math
|
||||
```
|
||||
|
||||
We also need to define a build target for our manifest sources:
|
||||
|
||||
``` python
|
||||
# src/chrome/services/math/public/cpp/BUILD.gn
|
||||
|
||||
source_set("manifest") {
|
||||
sources = [
|
||||
"manifest.cc",
|
||||
"manifest.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"//base",
|
||||
"//chrome/services/math/public/mojom",
|
||||
"//services/service_manager/public/cpp",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The above `Manifest` definition declares that the service is named `math` and
|
||||
that it **exposes** a single **capability** named `arithmetic` which allows
|
||||
access to the `Divider` interface.
|
||||
|
||||
Another service may **require** this capability from its own manifest in order
|
||||
for the Service Manager to grant it access to a `Divider`. We'll see this a
|
||||
few sections below. First, let's get the manifest and service implementation
|
||||
registered with Chromium's Service Manager.
|
||||
|
||||
### Registering the Manifest
|
||||
|
||||
For the most common out-of-process service cases, we register service manifests
|
||||
by **packaging** them in Chrome. This can be done by augmenting the value
|
||||
returned by
|
||||
[`GetChromePackagedServiceManifests`](https://cs.chromium.org/chromium/src/chrome/app/chrome_packaged_service_manifests.cc?rcl=af43cabf3c01e28be437becb972a7eae44fd54e8&l=133).
|
||||
|
||||
We can add our manifest there:
|
||||
|
||||
``` cpp
|
||||
// Deep within src/chrome/app/chrome_packaged_service_manifests.cc...
|
||||
const std::vector<service_manager::Manifest>
|
||||
GetChromePackagedServiceManifests() {
|
||||
...
|
||||
math::GetManifest(),
|
||||
...
|
||||
```
|
||||
|
||||
And don't forget to add a GN dependency from
|
||||
[`//chrome/app:chrome_packaged_service_manifests`](https://cs.chromium.org/chromium/src/chrome/app/BUILD.gn?l=564&rcl=a77d5ba9c4621cfe14e7e1cd03bbae16904f269e) onto
|
||||
`//chrome/services/math/public/cpp:manifest`!
|
||||
|
||||
We're almost done with service setup. The last step is to teach Chromium (and
|
||||
thus the Service Manager) how to launch an instance of our beautiful `math`
|
||||
service.
|
||||
Now we have a fully defined `MathService` implementation that we can make
|
||||
available in- or out-of-process.
|
||||
|
||||
### Hooking Up the Service Implementation
|
||||
|
||||
There are two parts to this for an out-of-process Chrome service.
|
||||
|
||||
First, we need
|
||||
to inform the embedded Service Manager that this service is an out-of-process
|
||||
service. The goofiness of this part is a product of some legacy issues and it
|
||||
should be eliminated soon, but for now it just means teaching the Service
|
||||
Manager how to *label* the process it creates for this service (e.g. how the process will
|
||||
appear in the system task manager). We modify
|
||||
[`ChromeContentBrowserClient::RegisterOutOfProcessServices`](https://cs.chromium.org/chromium/src/chrome/browser/chrome_content_browser_client.cc?rcl=960886a7febcc2acccea7f797d3d5e03a344a12c&l=3766)
|
||||
for this:
|
||||
For an out-of-process Chrome service, we simply register a factory function
|
||||
in [`//chrome/utility/services.cc`](https://cs.chromium.org/chromium/src/chrome/utility/services.cc).
|
||||
|
||||
``` cpp
|
||||
void ChromeContentBrowserClient::RegisterOutOfProcessServices(
|
||||
OutOfProcessServicesMap* services) {
|
||||
...
|
||||
|
||||
(*services)[math::mojom::kServiceName] =
|
||||
base::BindRepeating([]() -> base::string16 {
|
||||
return "Math Service";
|
||||
});
|
||||
|
||||
...
|
||||
auto RunMathService(mojo::PendingReceiver<math::mojom::MathService> receiver) {
|
||||
return std::make_unique<math::MathService>(std::move(receiver));
|
||||
}
|
||||
|
||||
mojo::ServiceFactory* GetMainThreadServiceFactory() {
|
||||
// Existing factories...
|
||||
static base::NoDestructor<mojo::ServiceFactory> factory {
|
||||
RunFilePatcher,
|
||||
RunUnzipper,
|
||||
|
||||
// We add our own factory to this list
|
||||
RunMathService,
|
||||
//...
|
||||
```
|
||||
|
||||
And finally, since nearly all out-of-process services run in a "utility" process
|
||||
today, we need to add a dependency on our actual `Service` implementation to
|
||||
Chrome's service spawning code within the utility process.
|
||||
With this done, it is now possible for the browser process to launch new
|
||||
out-of-process instances of MathService.
|
||||
|
||||
For this step we just modify
|
||||
[`ChromeContentUtilityClient::MaybeCreateMainThreadService`](https://cs.chromium.org/chromium/src/chrome/utility/chrome_content_utility_client.cc?rcl=7226adebd6e8d077d673a82acf1aab0790627178&l=261)
|
||||
by adding a block of code as follows:
|
||||
### Launching the Service
|
||||
|
||||
If you're running your service in-process, there's really nothing interesting
|
||||
left to do. You can instantiate the service implementation just like any other
|
||||
object, yet you can also talk to it via a Mojo Remote as if it were
|
||||
out-of-process.
|
||||
|
||||
To launch an out-of-process service instance after the hookup performed in the
|
||||
previous section, use Content's
|
||||
[`ServiceProcessHost`](https://cs.chromium.org/chromium/src/content/public/browser/service_process_host.h?rcl=e7a1f6c9a24f3151c875598174a05167fb12c5d5&l=47)
|
||||
API:
|
||||
|
||||
``` cpp
|
||||
std::unique_ptr<service_manager::Service> ChromeContentUtilityClient::MaybeCreateMainThreadService(
|
||||
const std::string& service_name,
|
||||
service_manager::mojom::ServiceRequest request) {
|
||||
...
|
||||
|
||||
if (service_name == math::mojom::kServiceName)
|
||||
return std::make_unique<math::MathService>(std::move(request));
|
||||
|
||||
...
|
||||
}
|
||||
mojo::Remote<math::mojom::MathService> math_service =
|
||||
content::ServiceProcessHost::Launch<math::mojom::MathService>(
|
||||
content::ServiceProcessHost::LaunchOptions()
|
||||
.WithSandboxType(content::SandboxType::kUtility)
|
||||
.WithDisplayName("Math!")
|
||||
.Pass());
|
||||
```
|
||||
|
||||
And we're done!
|
||||
Except in the case of crashes, the launched process will live as long as
|
||||
`math_service` lives. As a corollary, you can force the process to be torn
|
||||
down by destroying (or resetting) `math_service`.
|
||||
|
||||
As one nice follow-up step, let's use our math service from the browser.
|
||||
|
||||
### Using the Service
|
||||
|
||||
We can grant the browser process access to our `Divider` interface by
|
||||
**requiring** the `math` service's `arithmetic` capability within the
|
||||
`content_browser` service manifest.
|
||||
|
||||
*** aside
|
||||
NOTE: See the following section for an elaboration on what `content_browser` is.
|
||||
For the sake of this example, it's magic.
|
||||
***
|
||||
|
||||
For Chrome-specific features such as our glorious new `math` service, we can
|
||||
amend the `content_browser` manifest by modifying
|
||||
[GetChromeContentBrowserOverlayManifest](https://cs.chromium.org/chromium/src/chrome/app/chrome_content_browser_overlay_manifest.cc?rcl=38db90321e8e3627b2f3165cdb051fa8d668af48&l=100)
|
||||
as follows:
|
||||
We can now perform an out-of-process division:
|
||||
|
||||
``` cpp
|
||||
// src/chrome/app/chrome_content_browser_overlay_manifest.cc
|
||||
|
||||
...
|
||||
const service_manager::Manifest& GetChromeContentBrowserOverlayManifest() {
|
||||
...
|
||||
.RequireCapability(math::mojom::kServiceName,
|
||||
math::mojom::kArithmeticCapability)
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Finally, we can use the global `content_browser` instance's `Connector` to send
|
||||
an interface request to our service. This is accessible from the main thread of
|
||||
the browser process. Somewhere in `src/chrome/browser`, we can write:
|
||||
|
||||
``` cpp
|
||||
// This gives us the system Connector for the browser process, which has access
|
||||
// to most service interfaces.
|
||||
service_manager::Connector* connector = content::GetSystemConnector();
|
||||
|
||||
// Recall from the earlier Mojo section that BindNewPipeAndPassReceiver()
|
||||
// creates a message pipe and yields a PendingReceiver as its return value.
|
||||
// Connector passes the receiver endpoint to the Service Manager along with
|
||||
// the name of our target service, "math".
|
||||
mojo::Remote<math::mojom::Divider> divider;
|
||||
connector->Connect(math::mojom::kServiceName,
|
||||
divider.BindNewPipeAndPassReceiver());
|
||||
|
||||
// As a client, we do not have to wait for any acknowledgement or confirmation
|
||||
// of a connection. We can start queueing messages immediately and they will be
|
||||
// delivered as soon as the service is up and running.
|
||||
divider->Divide(
|
||||
// NOTE: As a client, we do not have to wait for any acknowledgement or
|
||||
// confirmation of a connection. We can start queueing messages immediately and
|
||||
// they will be delivered as soon as the service is up and running.
|
||||
math_service->Divide(
|
||||
42, 6, base::BindOnce([](int32_t quotient) { LOG(INFO) << quotient; }));
|
||||
```
|
||||
*** aside
|
||||
NOTE: To ensure the execution of the response callback, the
|
||||
mojo::Remote<Divider> object must be kept alive (see
|
||||
`mojo::Remote<math::mojom::MathService>` object must be kept alive (see
|
||||
[this section](/mojo/public/cpp/bindings/README.md#A-Note-About-Endpoint-Lifetime-and-Callbacks)
|
||||
and [this note from an earlier section](#sending-a-message)).
|
||||
***
|
||||
|
||||
This should successfully spawn a new process to run the `math` service if it's
|
||||
not already running, then ask it to do a division, and ultimately log the result
|
||||
after it's sent back to the browser process.
|
||||
|
||||
Finally it's worth reiterating that every service instance in the system has
|
||||
its own `Connector` and there's no reason we have to limit ourselves to
|
||||
`content_browser` as the client, as long as the appropriate manifest declares
|
||||
that it requires our `arithmetic` capability.
|
||||
|
||||
If we did not update the `content_browser` manifest overlay as we did in this
|
||||
example, the `Divide` call would never reach the `math` service (in fact the
|
||||
service wouldn't even be started) and instead we'd get an error message (or in
|
||||
developer builds, an assertion failure) informing us that the Service Manager
|
||||
blocked the `Connect` call.
|
||||
|
||||
## Content-Layer Services Overview
|
||||
|
||||
Apart from very early initialization steps in the browser process, every bit of
|
||||
logic in Chromium today is effectively running as part of one service instance
|
||||
or another.
|
||||
|
||||
Although we continue to migrate parts of the browser's privileged
|
||||
functionality to more granular services defined below the Content layer, the
|
||||
main services defined in Chromium today continue to model the Content layer's
|
||||
classical multiprocess architecture which defines a handful of
|
||||
**process types**: browser, renderer, gpu, utility, and plugin processes. For
|
||||
each of these process types, we now define corresponding services.
|
||||
|
||||
Manifest definitions for all of the following services can be found in
|
||||
`//content/public/app`.
|
||||
|
||||
### The Browser Service
|
||||
|
||||
`content_browser` is defined to encapsulate general-purpose browser process
|
||||
code. There are multiple instances of this service, all running within the
|
||||
singular browser process. There is one shared global instance as well an
|
||||
additional instance for each `BrowserContext` (*i.e.* per Chrome profile).
|
||||
|
||||
The global instance exists primarily so that arbitrary browser process code can
|
||||
reach various system services conveniently via a global `Connector` instance
|
||||
on the main thread.
|
||||
|
||||
Each instance associated with a `BrowserContext` is placed in an isolated
|
||||
instance group specific to that `BrowserContext`. This limits the service
|
||||
instances with which its `Connector` can make contact. These instances are
|
||||
used primarily to facilitate the spawning of other isolated per-profile service
|
||||
instances, such as renderers and plugins.
|
||||
|
||||
### The Renderer Service
|
||||
|
||||
A `content_renderer` instance is spawned in its own sandboxed process for every
|
||||
site-isolated instance of Blink we require. Instances are placed in the same
|
||||
instance group as the renderer's corresponding `BrowserContext`, *i.e.* the
|
||||
profile which navigated to the site being rendered.
|
||||
|
||||
Most interfaces used by `content_renderer` are not brokered through the Service
|
||||
Manager but instead are brokered through dedicated interfaces implemented by
|
||||
`content_browser`, with which each renderer maintains persistent connections.
|
||||
|
||||
### The GPU Service
|
||||
|
||||
Only a single instance of `content_gpu` exists at a time and it always runs in
|
||||
its own isolated, sandboxed process. This service hosts the code in content/gpu
|
||||
and whatever else Content's embedder adds to that for GPU support.
|
||||
|
||||
### The Plugin Service
|
||||
|
||||
`content_plugin` hosts a plugin in an isolated process. Similarly to
|
||||
`content_renderer` instances, each instance of `content_plugin` belongs to
|
||||
an instance group associated with a specific `BrowserContext`, and in general
|
||||
plugins get most of their functionality by talking directly to `content_browser`
|
||||
rather than brokering interface requests through the Service Manager.
|
||||
|
||||
### The Utility Service
|
||||
|
||||
`content_utility` exists only nominally today, as there is no remaining API
|
||||
surface within Content which would allow a caller to explicitly create an
|
||||
instance of it. Instead, this service is used exclusively to bootstrap new
|
||||
isolated processes in which other services will run.
|
||||
|
||||
## Exposing Interfaces Between Content Processes
|
||||
|
||||
Apart from the standard Service Manager APIs, the Content layer defines a number
|
||||
of additional concepts for Content and its embedder to expose interfaces
|
||||
specifically between Content processes in various contexts.
|
||||
|
||||
### Exposing Browser Interfaces to Renderer Documents and Workers
|
||||
|
||||
Documents and workers are somewhat of a special case since interface access
|
||||
decisions often require browser-centric state that the Service Manager cannot
|
||||
know about, such as details of the current `BrowserContext`, the origin of the
|
||||
renderered content, installed extensions in the renderer, *etc.* For this
|
||||
reason, interface brokering decisions are increasingly being made by the
|
||||
browser.
|
||||
|
||||
#### Interface Brokers
|
||||
### Interface Brokers
|
||||
|
||||
We define an explicit mojom interface with a persistent connection
|
||||
between a renderer's frame object and the corresponding
|
||||
@ -760,53 +456,6 @@ void PopulateFrameBinders(RenderFrameHostImpl* host,
|
||||
|
||||
TODO: add information about workers and embedders.
|
||||
|
||||
### Exposing Browser Interfaces to Render Processes
|
||||
|
||||
Sometimes (albeit rarely) it's useful to expose a browser interface directly to
|
||||
a renderer process. This can be done as for any other interface exposed between
|
||||
two services. In this specific instance, the `content_browser` manifest exposes
|
||||
a capability named `"renderer"` which `content_renderer` requires. Any interface
|
||||
listed as part of that capability can be accessed by a `content_renderer`
|
||||
instance by using its own `Connector`. See below.
|
||||
|
||||
### Exposing Browser Interfaces to Content Child Processes
|
||||
|
||||
All Content child process types (renderer, GPU, and plugin) share a common API
|
||||
to interface with the Service Manager. Their Service Manager connection is
|
||||
initialized and maintained by `ChildThreadImpl` on process startup, and from
|
||||
the main thread, you can access the process's `Connector` as follows:
|
||||
|
||||
``` cpp
|
||||
auto* connector = content::ChildThread::Get()->GetConnector();
|
||||
|
||||
// For example...
|
||||
connector->Connect(content::mojom::kBrowserServiceName,
|
||||
std::move(some_receiver));
|
||||
```
|
||||
|
||||
### Exposing Content Child Process Interfaces to the Browser
|
||||
|
||||
Content child processes may also expose interfaces to the browser, though this
|
||||
is much less common and requires a fair bit of caution since the browser must be
|
||||
careful to only call `Connector.BindInterface` in these cases with an exact
|
||||
`service_manager::Identity` to avoid unexpected behavior.
|
||||
|
||||
Every child process provides a subclass of ChildThreadImpl, and this can be used
|
||||
to install a new `ConnectionFilter` on the process's Service Manager connection
|
||||
before starting to accept requests.
|
||||
|
||||
This behavior should really be considered deprecated, but for posterity, here is
|
||||
how the GPU process does it:
|
||||
|
||||
1. [Disable Service Manager connection auto-start](https://cs.chromium.org/chromium/src/content/gpu/gpu_child_thread.cc?rcl=6b85a56334c0cd64b0e657934060de716714ca64&l=62)
|
||||
2. [Register a new ConnectionFilter impl to handle certain interface requests](https://cs.chromium.org/chromium/src/content/gpu/gpu_child_thread.cc?rcl=6b85a56334c0cd64b0e657934060de716714ca64&l=255)
|
||||
3. [Start the Service Manager connection manually](https://cs.chromium.org/chromium/src/content/gpu/gpu_child_thread.cc?rcl=6b85a56334c0cd64b0e657934060de716714ca64&l=257)
|
||||
|
||||
It's much more common instead for there to be some primordial interface
|
||||
connection established by the child process which can then be used to facilitate
|
||||
push communications from the browser, so please consider not duplicating this
|
||||
behavior.
|
||||
|
||||
## Additional Support
|
||||
|
||||
If this document was not helpful in some way, please post a message to your
|
||||
|
@ -28,26 +28,15 @@ command-line switch. Client code using the Network Service stays the same,
|
||||
independent of that switch.
|
||||
|
||||
This document focuses on helpful guidelines and patterns for servicifying parts
|
||||
of Chromium, taking into account some nuances in how Chromium models its core
|
||||
services as well as how it embeds and configures the Service Manager. Readers
|
||||
are strongly encouraged to first read some basic
|
||||
[Service Manager documentation](/services/service_manager/README.md), as it will
|
||||
likely make the contents of this document easier to digest.
|
||||
of Chromium.
|
||||
|
||||
Also see general [Mojo & Services](/docs/README.md#Mojo-Services)
|
||||
documentation for other introductory guides, API references, *etc.*
|
||||
|
||||
## Setting Up The Service
|
||||
|
||||
There are three big things you must decide when building and hooking up a shiny
|
||||
new service:
|
||||
|
||||
- Where should the service live in the tree?
|
||||
- Do you need an instance of your service per BrowserContext?
|
||||
- Can Content depend on your service, or must Content embedders like Chrome do
|
||||
so independently?
|
||||
|
||||
This section aims to help you understand and answer those questions.
|
||||
This section briefly covers early decisions and implementation concerns when
|
||||
introducing a new service.
|
||||
|
||||
### Where in the Tree?
|
||||
|
||||
@ -72,115 +61,51 @@ Other common places where developers place services, and why:
|
||||
just Chrome itself (for example, if the `ash` service must also connect to
|
||||
them for use in system UI).
|
||||
|
||||
### Inside Content or Not?
|
||||
### Launching Service Processes
|
||||
|
||||
The next decision you need to make is whether or not Content will wire in your
|
||||
service directly -- that is, whether or not your service is necessary to support
|
||||
some subsystem Content makes available to either the web platform or to Content
|
||||
embedders like Chrome, Android WebView, Cast Shell, and various third-party
|
||||
applications.
|
||||
Content provides a simple
|
||||
[`ServiceProcessHost`](https://cs.chromium.org/chromium/src/content/public/browser/service_process_host.h?rcl=723edf64a56ef6058e886afc67adc786bea39e78&l=47)
|
||||
API to launch a new Service Process. The Mojo Remote corresponding to each
|
||||
process launch is effectively a lifetime control for the launched process.
|
||||
|
||||
For example, Content cannot function at all without the Network Service being
|
||||
available, because Content depends heavily on the Network Service to issue and
|
||||
process all of its network requests (imagine that, right?). As such, the
|
||||
Network Service is wired up to the Service Manager from within Content directly.
|
||||
In general, services which will be wired up in Content must live either in
|
||||
`//services` or `//components/services` but ideally the former.
|
||||
You may choose to maintain only a single concurrent instance of your service
|
||||
at a time, similar to the Network or Storage services. In this case, typically
|
||||
you will have some browser code maintain a lazy Mojo Remote to the service
|
||||
process, and any clients of the service will have their connections brokered
|
||||
through this interface.
|
||||
|
||||
Conversely there are a large number of services used only by Chrome today,
|
||||
such as the `unzip` service which safely performs sandboxed unpacking of
|
||||
compressed archive files on behalf of clients in the browser process. These
|
||||
can always be placed in `//chrome/services`.
|
||||
In other cases you may want to manage multiple independent service processes.
|
||||
The Data Decoder service, for example, allows for arbitrary browser code
|
||||
to launch a unique isolated instance to process a single decode operation or
|
||||
a batch of related operations (e.g. to decode a bunch of different objects
|
||||
from the same untrusted origin).
|
||||
|
||||
### Per-BrowserContext or Not?
|
||||
Insofar as the browser can use ServiceProcessLauncher however it likes, and the
|
||||
corresponding Mojo Remotes can be owned just like any other object, developers
|
||||
are free to manage their service instances however they like.
|
||||
|
||||
Now that you've decided on a source location for your service and you know
|
||||
whether it will be wired into Content or hooked up by Content embedder code, all
|
||||
that's left left is to decide whether or not you want an instance of your service
|
||||
per BrowserContext (*i.e.* per user profile in Chrome).
|
||||
### Hooking Up the Service Implementation
|
||||
|
||||
The alternative is for you to manage your own instance arity, either as a
|
||||
singleton service (quite common) or as a service which supports multiple
|
||||
instances that are *not* each intrinsically tied to a BrowserContext. Most
|
||||
services choose this path because BrowserContext coupling is typically
|
||||
unnecessary.
|
||||
For out-of-process service launching, Content uses its "utility" process type.
|
||||
|
||||
As a general rule, if you're porting a subsystem which today relies heavily
|
||||
on `BrowserContextKeyedService`, it's likely that you want your service
|
||||
instances to have a 1:1 correspondence with BrowserContext instances.
|
||||
For services known to content, this is accomplished by adding an appropriate
|
||||
factory function to
|
||||
[`//content/utility/services.cc`](https://cs.chromium.org/chromium/src/content/utility/services.cc)
|
||||
|
||||
### Putting It All Together
|
||||
For other services known only to Chrome, we have a similar file at
|
||||
[`//chrome/utility/services.cc`](https://cs.chromium.org/chromium/src/chrome/utility/services.cc).
|
||||
|
||||
Let's get down to brass tacks. You're a developer of action. You've made all the
|
||||
important choices you need to make and you've even built a small and extremely
|
||||
well-tested prototype service with the help of
|
||||
[this glorious guide](/services/service_manager/README.md#Services). Now you
|
||||
want to get it working in Chromium while suffering as little pain as possible.
|
||||
Once an appropriate service factory is registered for your main service
|
||||
interface in one of these places, `ServiceProcessHost::Launch` can be used to
|
||||
acquire a new isolated instance from within the browser process.
|
||||
|
||||
You're not going to believe it, but this section was written *just for YOU*.
|
||||
To run a service in-process, you can simply instantiate your service
|
||||
implementation (e.g. on a background thread) like you would any other object,
|
||||
and you can then bind a Mojo Remote which is connected to that instance.
|
||||
|
||||
For services which are **are not** isolated per BrowserContext and which **can**
|
||||
be wired directly into Content:
|
||||
|
||||
- Include your service's manifest in the `content_packaged_services` manifest
|
||||
directly, similar to
|
||||
[these ones](https://cs.chromium.org/chromium/src/content/public/app/content_packaged_services_manifest.cc?rcl=0e8ac57eec2acfaa6f44b06eaa2fa667fe84a293&l=63).
|
||||
- If you want to run your service embedded in the browser process, follow the
|
||||
examples using `RegisterInProcessService`
|
||||
[here](https://cs.chromium.org/chromium/src/content/browser/service_manager/service_manager_context.cc?rcl=0e8ac57eec2acfaa6f44b06eaa2fa667fe84a293&l=589).
|
||||
- If you want to run your service out-of-process, update
|
||||
`out_of_process_services`
|
||||
[like so](https://cs.chromium.org/chromium/src/content/browser/service_manager/service_manager_context.cc?rcl=0e8ac57eec2acfaa6f44b06eaa2fa667fe84a293&l=642)
|
||||
and hook up your actual private `Service` implementation exactly like the
|
||||
many examples
|
||||
[here](https://cs.chromium.org/chromium/src/content/utility/utility_service_factory.cc?rcl=2bdcc80a55c72a26ffe9778681f98dc4b6a565c0&l=114).
|
||||
|
||||
For services which are **are** isolated per BrowserContext and which **can**
|
||||
be wired directly into Content:
|
||||
|
||||
- Include your service's manifest in the `content_browser` manifest directly,
|
||||
similar to
|
||||
[these ones](https://cs.chromium.org/chromium/src/content/public/app/content_browser_manifest.cc?rcl=a651619623a7b56d0c21083463ef8e61bf0a6058&l=277).
|
||||
- If you want to run your service embedded in the browser process, follow the
|
||||
example
|
||||
[here](https://cs.chromium.org/chromium/src/content/browser/browser_context.cc?rcl=a651619623a7b56d0c21083463ef8e61bf0a6058&l=250)
|
||||
- If you want to run your service out-of-process, you are doing something that
|
||||
hasn't been done yet and you will need to build a new thing.
|
||||
|
||||
For services which **are not** isolated per BrowserContext but which **can not**
|
||||
be wired directly into Content:
|
||||
|
||||
- Include your service's manifest in Chrome's `content_packaged_services`
|
||||
manifest overlay similar to
|
||||
[these ones](https://cs.chromium.org/chromium/src/chrome/app/chrome_packaged_service_manifests.cc?rcl=a651619623a7b56d0c21083463ef8e61bf0a6058&l=135)
|
||||
- If you want to run your service embedded in the browser process, follow the
|
||||
examples in `ChromeContentBrowserClient::HandleServiceRequest`
|
||||
[here](https://cs.chromium.org/chromium/src/chrome/browser/chrome_content_browser_client.cc?rcl=a651619623a7b56d0c21083463ef8e61bf0a6058&l=3891)
|
||||
- If you want to run your service out-of-process, modify
|
||||
`ChromeContentBrowserClient::RegisterOutOfProcessServices` like the examples
|
||||
[here](https://cs.chromium.org/chromium/src/chrome/browser/chrome_content_browser_client.cc?rcl=a651619623a7b56d0c21083463ef8e61bf0a6058&l=3785)
|
||||
and hook up your `Service` implementation in
|
||||
`ChromeContentUtilityClient::HandleServiceRequest` like the ones
|
||||
[here](https://cs.chromium.org/chromium/src/chrome/utility/chrome_content_utility_client.cc?rcl=a651619623a7b56d0c21083463ef8e61bf0a6058&l=237).
|
||||
|
||||
For services which **are** isolated per BrowserContext but which **can not** be
|
||||
wired directly into Content:
|
||||
|
||||
- Include your service's manifest in Chrome's `content_browser` manifest overlay
|
||||
similar to
|
||||
[these ones](https://cs.chromium.org/chromium/src/chrome/app/chrome_content_browser_overlay_manifest.cc?rcl=a651619623a7b56d0c21083463ef8e61bf0a6058&l=247)
|
||||
- If you want to run your service embedded in the browser process, follow the
|
||||
examples in `ProfileImpl::HandleServiceRequest`
|
||||
[here](https://cs.chromium.org/chromium/src/chrome/browser/profiles/profile_impl.cc?rcl=350aea1a0242c2ea8610c9f755acee085c74ea7d&l=1263)
|
||||
- If you want to run your service out-of-process, you are doing something that
|
||||
hasn't been done yet and you will need to build a new thing.
|
||||
|
||||
*** aside
|
||||
The non-Content examples above are obviously specific to Chrome as the embedder,
|
||||
but Chrome's additions to supported services are all facilitated through the
|
||||
common `ContentBrowserClient` and `ContentUtilityClient` APIs that all embedders
|
||||
can implement. Mimicking what Chrome does should be sufficient for any embedder.
|
||||
***
|
||||
This is useful if you want to avoid the overhead of extra processes in some
|
||||
scenarios, and it allows the detail of where and how the service runs to be
|
||||
fully hidden behind management of the main interface's Mojo Remote.
|
||||
|
||||
## Incremental Servicification
|
||||
|
||||
@ -259,30 +184,9 @@ doubt, look approximately near the recommended bits of code and try to find
|
||||
relevant prior art.
|
||||
***
|
||||
|
||||
Services are supported on iOS, with the usage model in //ios/web being very
|
||||
close to the usage model in //content. More specifically:
|
||||
|
||||
* To embed a global service in the browser service, override
|
||||
[WebClient::RegisterServices](https://cs.chromium.org/chromium/src/ios/web/public/web_client.h?q=WebClient::Register&sq=package:chromium&l=136). For an example usage, see
|
||||
[ShellWebClient](https://cs.chromium.org/chromium/src/ios/web/shell/shell_web_client.mm?q=ShellWebClient::RegisterS&sq=package:chromium&l=91)
|
||||
and the related integration test that
|
||||
[connects to the embedded service](https://cs.chromium.org/chromium/src/ios/web/shell/test/service_manager_egtest.mm?q=service_manager_eg&sq=package:chromium&l=89).
|
||||
* To embed a per-BrowserState service, override
|
||||
[BrowserState::RegisterServices](https://cs.chromium.org/chromium/src/ios/web/public/browser_state.h?q=BrowserState::RegisterServices&sq=package:chromium&l=89). For an
|
||||
example usage, see
|
||||
[ShellBrowserState](https://cs.chromium.org/chromium/src/ios/web/shell/shell_browser_state.mm?q=ShellBrowserState::RegisterServices&sq=package:chromium&l=48)
|
||||
and the related integration test that
|
||||
[connects to the embedded service](https://cs.chromium.org/chromium/src/ios/web/shell/test/service_manager_egtest.mm?q=service_manager_eg&sq=package:chromium&l=110).
|
||||
* To register a per-frame Mojo interface, override
|
||||
[WebClient::BindInterfaceRequestFromMainFrame](https://cs.chromium.org/chromium/src/ios/web/public/web_client.h?q=WebClient::BindInterfaceRequestFromMainFrame&sq=package:chromium&l=148).
|
||||
For an example usage, see
|
||||
[ShellWebClient](https://cs.chromium.org/chromium/src/ios/web/shell/shell_web_client.mm?type=cs&q=ShellWebClient::BindInterfaceRequestFromMainFrame&sq=package:chromium&l=115)
|
||||
and the related integration test that
|
||||
[connects to the interface](https://cs.chromium.org/chromium/src/ios/web/shell/test/service_manager_egtest.mm?q=service_manager_eg&sq=package:chromium&l=130).
|
||||
Note that this is the equivalent of
|
||||
[ContentBrowserClient::BindInterfaceRequestFromFrame()](https://cs.chromium.org/chromium/src/content/public/browser/content_browser_client.h?type=cs&q=ContentBrowserClient::BindInterfaceRequestFromFrame&sq=package:chromium&l=667),
|
||||
as on iOS all operation "in the content area" is implicitly operating in the
|
||||
context of the page's main frame.
|
||||
Services are supported on iOS insofar as Mojo is supported. However, Chrome on
|
||||
iOS is strictly single-process, and all services thus must run in-process on
|
||||
iOS.
|
||||
|
||||
If you have a use case or need for services on iOS, contact
|
||||
blundell@chromium.org. For general information on the motivations and vision for
|
||||
|
@ -4,11 +4,6 @@
|
||||
|
||||
## Overview
|
||||
|
||||
If you're looking for general documentation on the Service Manager, what a
|
||||
"service" is, and how to build one, see the
|
||||
[Service Manager & Services](/services/service_manager/README.md)
|
||||
documentation instead of this document.
|
||||
|
||||
The top-level `//services` directory contains the sources, public Mojo interface
|
||||
definitions, and public client libraries for a number of essential services,
|
||||
designated as **Chrome Foundation Services**. If you think of Chrome as a
|
||||
@ -17,9 +12,6 @@ services of that OS.
|
||||
|
||||
Each subdirectory here corresponds to a service that:
|
||||
|
||||
- implements
|
||||
[`Service`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/service.h?rcl=ebec02bcca6e327b2e01855ce43ab1bec0aeef27&l=21)
|
||||
and is thus a client of the Service Manager
|
||||
- generally focuses on a subset of functionality or features which are
|
||||
thematically or functionally related in a way that makes sense given the name
|
||||
of the service
|
||||
@ -29,16 +21,12 @@ Each subdirectory here corresponds to a service that:
|
||||
*** aside
|
||||
Note that there are other parts of the tree which aggregate
|
||||
slightly-less-than-foundational service definitions, such as services specific
|
||||
to the Chrome browser, defined in `//chrome/services`. The motivations, advice,
|
||||
and standards discussed in this document apply to all service definitions in the
|
||||
Chromium tree.
|
||||
to the Chrome browser defined in `//chrome/services` or reusable services for
|
||||
Content or its embedders, defined in `//components/services`. The motivations,
|
||||
advice, and standards discussed in this document apply to all service
|
||||
definitions in the Chromium tree.
|
||||
***
|
||||
|
||||
The `//services/service_manager` directory contains the implementation and
|
||||
public APIs of the Service Manager itself, including an embedder API with which
|
||||
software applications (such as Chrome) can embed the Service Manager to manage
|
||||
their own multiprocess service architecture.
|
||||
|
||||
One of the main motivations for expressing Chromium as a collection of services
|
||||
is long-term maintainability and code health. Because service API boundaries are
|
||||
strictly limited to Mojo interfaces, state owned and managed by each service is
|
||||
@ -47,10 +35,10 @@ strongly isolated from other components in the system.
|
||||
Another key motivation is general modularity and reusability: in the past there
|
||||
have been a number of missed opportunities for potential new features or
|
||||
Chromium-based products due to the browser's generally monolothic and inflexible
|
||||
system design. With the Service Manager & services providing scaffolding for
|
||||
system components, it becomes progressively easier to build out newer use cases
|
||||
with *e.g.* a smaller resource footprint, or a different process model, or even
|
||||
a more granular binary distribution.
|
||||
system design. With the services providing scaffolding for system components, it
|
||||
becomes progressively easier to build out newer use cases with *e.g.* a smaller
|
||||
resource footprint, or a different process model, or even a more granular binary
|
||||
distribution.
|
||||
|
||||
## Service Standards
|
||||
|
||||
@ -126,19 +114,13 @@ expose `GoatTeleporterFactory` instead. Now it's impossible for a client to
|
||||
acquire a functioning `GoatTeleporter` pipe without also providing a
|
||||
corresponding client pipe to complement it.
|
||||
|
||||
### Service & Interface Naming
|
||||
### Interface Naming
|
||||
|
||||
Just some basic tips for service and interface naming:
|
||||
|
||||
- Strive to give your service a name that makes it immediately obvious what the
|
||||
service is for (*e.g.*, `"network"`, `"metrics"`) rather than a meaningless
|
||||
codename like `"cromulator_3000"`.
|
||||
- Avoid the usage of `"Service"` in interface names. While the term "service"
|
||||
is overloaded in Chromium and certainly has plenty of valid interpretations,
|
||||
in the context of Service Manager services it has a very specific meaning
|
||||
and should not be overloaded further if possible. An interface which exposes a
|
||||
control API for a goat teleporter can just be called `GoatTeleporter`, not
|
||||
`GoatTeleporterService`.
|
||||
- Strive to give your service's main interface a name that directly conveys the
|
||||
general purpose of the service (*e.g.*, `NetworkService`, `StorageService`)
|
||||
rather than a meaningless codename like `Cromulator`.
|
||||
|
||||
- Strive to avoid conceptual layering violations in naming and documentation --
|
||||
*e.g.*, avoid referencing Blink or Content concepts like "renderers" or
|
||||
@ -170,9 +152,7 @@ system.
|
||||
|
||||
Generally the language-specific client libraries are built against only the
|
||||
public mojom API of the service (and usually few other common dependencies like
|
||||
`//base` and `//mojo`), and they bootstrap connections to those interfaces by
|
||||
using public Service Manager APIs like
|
||||
[`Connector`](/services/service_manager/README.md#Connectors).
|
||||
`//base` and `//mojo`).
|
||||
|
||||
Even in the private service implementation, services should not depend on very
|
||||
large components like Content, Chrome, or Blink.
|
||||
@ -219,21 +199,8 @@ actually required as part of the service implementation. For example
|
||||
what expectations and guarantees are supposed to be upheld by *any*
|
||||
implementation of the service's APIs.
|
||||
|
||||
- Take advantage of the
|
||||
[test support library](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/test/)
|
||||
provided by the Service Manager. In particular, `TestConnectorFactory` is
|
||||
useful for driving public API tests with the service running inside the test
|
||||
process, and `TestServiceManager` makes it possible to easily cover
|
||||
out-of-process testing scenarios while faking out as little of the system as
|
||||
possible.
|
||||
|
||||
## Adding a New Service
|
||||
|
||||
See the [Service Manager documentation](/services/service_manager/README.md) for
|
||||
more details regarding how to define a service and expose or consume interfaces
|
||||
to and from it, as well as how to make the service available to an application's
|
||||
runtime environment.
|
||||
|
||||
Please start a thread on
|
||||
[services-dev@chromium.org](https://groups.google.com/a/chromium.org/forum/#!forum/services-dev)
|
||||
if you want to propose the introduction of a new service.
|
||||
|
@ -1,929 +0,0 @@
|
||||
# The Service Manager & Services
|
||||
|
||||
[TOC]
|
||||
|
||||
## Overview
|
||||
|
||||
The Service Manager is a component which large applications like Chromium can
|
||||
use support a cross-platform, multi-process, service-oriented,
|
||||
hyphenated-adjective-laden architecture.
|
||||
|
||||
This document covers how to embed
|
||||
the Service Manager into an application as well as how to define and register
|
||||
services for it to manage. If you just want to read about defining services and
|
||||
using common service APIs, skip to the main [Services](#Services) section.
|
||||
|
||||
## Embedding the Service Manager
|
||||
|
||||
To embed the Service Manager, an application should link against the code in
|
||||
`//services/service_manager/embedder`. This defines a main entry point for
|
||||
most platforms, with a relatively small
|
||||
[`service_manager::MainDelegate`](https://cs.chromium.org/chromium/src/services/service_manager/embedder/main_delegate.h)
|
||||
interface for the application to implement. In particular, the application
|
||||
should at least implement
|
||||
[`GetServiceManifests`](https://cs.chromium.org/chromium/src/services/service_manager/embedder/main_delegate.h?rcl=734122d6a01196706dfc1c252fa09ed933778f8f&l=80) to provide
|
||||
metadata about the full set of services comprising the application.
|
||||
|
||||
*** aside
|
||||
Note that Chromium does not currently implement `GetServiceManifests` for
|
||||
production use of the Service Manager. This is because a bunch of process
|
||||
launching and management logic still lives at the Content layer. As more of this
|
||||
code moves into Service Manager internals, Chromium will start to look more like
|
||||
any other Service Manager embedder.
|
||||
***
|
||||
|
||||
*TODO: Improve embedder documentation here, and include support for in-process
|
||||
service launching once MainDelegate supports it.*
|
||||
|
||||
## Services
|
||||
|
||||
A **service** in this context can be defined as any self-contained body of
|
||||
application logic which satisfies *all* of the following constraints:
|
||||
|
||||
- It defines a single [implementation](#Implementation) of
|
||||
[`Service`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/service.h)
|
||||
to receive interface requests brokered by the
|
||||
Service Manager, and it maintains a connection between this object and the
|
||||
Service Manager using a
|
||||
[`ServiceBinding`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/service_binding.h).
|
||||
- Its API surface in from or out to other services is restricted exclusively to
|
||||
[Mojo](/mojo/README.md) interfaces and self-contained client libraries built
|
||||
on those Mojo interfaces. This means no link-time or run-time exposure of
|
||||
the service implementation's internal heap or global state.
|
||||
- It defines a [service manifest](#Manifests) to declare how the Service Manager
|
||||
should identify and manage instances of the service, as well as what
|
||||
interfaces are exposed to or required from other services in the system.
|
||||
|
||||
The Service Manager is responsible for managing the creation and interconnection
|
||||
of individual service instances, whether they are embedded within an existing
|
||||
process or each isolated within dedicated processes. Managed service processes
|
||||
may be sandboxed with any of various supported
|
||||
[sandbox configurations](#Sandbox-Configurations).
|
||||
|
||||
This section walks through important concepts and APIs throughout service
|
||||
development, and builds up a small working example service in the process.
|
||||
|
||||
### A Brief Note About Service Granularity
|
||||
|
||||
Many developers fret over what the right "size" or granularity is for a service
|
||||
or set of services. This makes sense, and there is always going to be some
|
||||
design tension between choosing a simpler and potentially more efficient,
|
||||
monolithic implementation, versus choosing a more modular but often more complex
|
||||
one.
|
||||
|
||||
One classic example of this tension is in the origins of Chromium's
|
||||
`device` service. The service hosts a number of independent device interfacing
|
||||
subsystems for things like USB, Bluetooth, HID, battery status, etc. You could
|
||||
easily imagine justifying separate services for each of these features, but it
|
||||
was ultimately decided keep them merged together as one service thematically
|
||||
related to hardware device capabilities. Some factors which played into this
|
||||
decision:
|
||||
|
||||
- There was no clear **security** benefit to keeping the features isolated from
|
||||
each other.
|
||||
- There was no clear **code size** benefit to keeping the features isolated from
|
||||
each other -- environments supporting any one of the device capabilities are
|
||||
fairly likely to support several others and would thus likely include all or
|
||||
most of the smaller services anyway.
|
||||
- There isn't really any coupling between the different features in the service,
|
||||
so there would be few **code health** benefits to building separate services.
|
||||
|
||||
Given all of the above conditions, opting for a smaller overall number of
|
||||
services seems likely to have been the right decision.
|
||||
|
||||
When making these kinds of decisions yourself, use your best judgment. When in
|
||||
doubt, start a bike-shedding centithread on
|
||||
[services-dev@chromium.org](https://groups.google.com/a/chromium.org/forum#!forum/services-dev).
|
||||
|
||||
### Implementation
|
||||
|
||||
The central fixture in any service implementation is, well, its
|
||||
[`Service`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/service.h)
|
||||
implementation. This is a small interface with really only three virtual methods
|
||||
of practical interest, all optional to implement:
|
||||
|
||||
``` cpp
|
||||
class Service {
|
||||
public:
|
||||
virtual void OnStart();
|
||||
virtual void OnBindInterface(const BindSourceInfo& source,
|
||||
const std::string& interface_name,
|
||||
mojo::ScopedMessagePipeHandle interface_pipe);
|
||||
virtual void OnDisconnected();
|
||||
};
|
||||
```
|
||||
|
||||
Services implement a subclass of this to work in conjunction with a
|
||||
[`ServiceBinding`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/service_binding.h)
|
||||
so the Service Manager can call into the service with lifecycle events and
|
||||
interface requests from other services.
|
||||
|
||||
*** aside
|
||||
NOTE: As discussed in [Instance Sharing](#Instance-Sharing) below, your service
|
||||
configuration may allow for the Service Manager to manage many concurrent
|
||||
instances of your service. Whether these instances run in the same shared
|
||||
process or in separate processes, each instance is comprised of exactly one
|
||||
dedicated instance of your actual `Service` subclass.
|
||||
***
|
||||
|
||||
Through the rest of this document we'll build out a basic working service
|
||||
implementation, complete with a manifest and simple tests. We'll call it the
|
||||
`storage` service, and it will provide the basis for all persistent storage
|
||||
capabilities in our crappy operating system hobby project that is doomed to
|
||||
languish forever in an unfinished state.
|
||||
|
||||
*** aside
|
||||
NOTE: Sheerly for the sake of brevity, example code written here is inlined in
|
||||
headers where it would typically be moved out-of-line.
|
||||
***
|
||||
|
||||
The first step is usually to imagine and define some mojom API surface to start
|
||||
with. We'll limit this example to two mojom files. It's conventional to keep
|
||||
important constants defined in a separate `constants.mojom` file:
|
||||
|
||||
``` cpp
|
||||
// src/services/storage/public/mojom/constants.mojom
|
||||
module storage.mojom;
|
||||
|
||||
// This string will identify our service to the Service Manager. It will be used
|
||||
// in our manifest when registering the service, and clients can use it when
|
||||
// sending interface requests to the Service Manager if they want to reach our
|
||||
// service.
|
||||
const string kServiceName = "storage";
|
||||
|
||||
// We'll use this later, in service manifest definitions.
|
||||
const string kAllocationCapability = "allocation";
|
||||
```
|
||||
|
||||
And some useful interface definitions:
|
||||
|
||||
``` cpp
|
||||
// src/services/storage/public/mojom/block.mojom
|
||||
module storage.mojom;
|
||||
|
||||
interface BlockAllocator {
|
||||
// Allocates a new block of persistent storage for the client. If allocation
|
||||
// fails, |receiver| is discarded.
|
||||
Allocate(uint64 num_bytes, pending_receiver<Block> receiver);
|
||||
};
|
||||
|
||||
interface Block {
|
||||
// Reads and returns a small range of bytes from the block.
|
||||
Read(uint64 byte_offset, uint16 num_bytes) => (array<uint8> bytes);
|
||||
|
||||
// Writes a small range of bytes to the block.
|
||||
Write(uint64 byte_offset, array<uint8> bytes);
|
||||
};
|
||||
```
|
||||
|
||||
And finally we'll define our basic `Service` subclass:
|
||||
|
||||
``` cpp
|
||||
// src/services/storage/storage_service.h
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "services/service_manager/public/cpp/service.h"
|
||||
#include "services/service_manager/public/cpp/service_binding.h"
|
||||
#include "services/storage/public/mojom/block.mojom.h"
|
||||
|
||||
namespace storage {
|
||||
|
||||
class StorageService : public service_manager::Service,
|
||||
public mojom::BlockAllocator {
|
||||
public:
|
||||
explicit StorageService(service_manager::mojom::ServiceRequest request)
|
||||
: service_binding_(this, std::move(request)) {}
|
||||
~StorageService() override = default;
|
||||
|
||||
private:
|
||||
// service_manager::Service:
|
||||
void OnBindInterface(const service_manager::BindSourceInfo& source,
|
||||
const std::string& interface_name,
|
||||
mojo::ScopedMessagePipeHandle interface_pipe) override {
|
||||
if (interface_name == mojom::BlockAllocator::Name_) {
|
||||
// If the Service Manager sends us a request with BlockAllocator's
|
||||
// interface name, we should treat |interface_pipe| as a
|
||||
// PendingReceiver<BlockAllocator> that we can bind.
|
||||
allocator_receivers_.Add(
|
||||
this, mojo::PendingReceiver<mojom::BlockAllocator>(std::move(interface_pipe)));
|
||||
}
|
||||
}
|
||||
|
||||
// mojom::BlockAllocator:
|
||||
void Allocate(uint64_t num_bytes, mojo::PendingReceiver<mojom::Block> receiver) override {
|
||||
// This space intentionally left blank.
|
||||
}
|
||||
|
||||
service_manager::ServiceBinding service_binding_;
|
||||
mojo::ReceiverSet<mojom::BlockAllocator> allocator_receivers_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StorageService);
|
||||
};
|
||||
|
||||
} // namespace storage
|
||||
```
|
||||
|
||||
Great. This is a basic service implementation. It does nothing useful, but we
|
||||
can come back and fix that some other time.
|
||||
|
||||
First, notice that the `StorageService` constructor takes a
|
||||
`service_manager::mojom::ServiceRequest` and immediately passes it to the
|
||||
`service_binding_` constructor. This is a nearly universal convention among
|
||||
service implementations, and your service will probably do it too. The
|
||||
`ServiceRequest` is an interface pipe that the Service Manager uses to drive
|
||||
your service, and the `ServiceBinding` is a helper class which translates
|
||||
messages from the Service Manager into the simpler interface methods of the
|
||||
`Service` class you've implemented.
|
||||
|
||||
`StorageService` also implements `OnBindInterface`, which is what the Service
|
||||
Manager invokes (via your `ServiceBinding`) when it has decided to route another
|
||||
service's interface request to your service instance. Note that because this is
|
||||
a generic API intended to support arbitrary interfaces, the request comes in the
|
||||
form of an interface name and a raw message pipe handle. It is the service's
|
||||
responsibility to inspect the name and decide how (or even if) to bind the pipe.
|
||||
Here we recognize only incoming `BlockAllocator` requests and drop anything
|
||||
else.
|
||||
|
||||
*** aside
|
||||
NOTE: Because interface receivers are just strongly-type message pipe endpoint
|
||||
wrappers, you can freely construct any kind of interface receiver over a raw
|
||||
message pipe handle. If you're planning to pass the endpoint around, it's good
|
||||
to do this as early as possible (i.e. as soon as you know the intended interface
|
||||
type) to benefit from your compiler's type-checking and avoid having to pass
|
||||
around both a name and a pipe.
|
||||
***
|
||||
|
||||
The last piece of our service that we need to lay down is its manifest.
|
||||
|
||||
### Manifests
|
||||
|
||||
A service's manifest is a simple static data structure provided to the Service
|
||||
Manager early during its initialization process. The Service Manager combines
|
||||
all of the manifest data it has in order to form a complete picture of the
|
||||
system it's coordinating. It uses all of this information to make decisions
|
||||
like:
|
||||
|
||||
- When service X requests interface Q from service Y, should it be allowed?
|
||||
- Were all of the constraints specified in X's request valid, and is X allowed
|
||||
to specify them as such?
|
||||
- Do I need to spawn a new instance of Y to satisfy this request or can I re-use
|
||||
an existing one (assuming there are any)?
|
||||
- If I have to spawn a new process for a new Y instance, how should I configure
|
||||
its sandbox, if at all?
|
||||
|
||||
All of this metadata is contained within different instances of the
|
||||
[`Manifest`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/manifest.h)
|
||||
class.
|
||||
|
||||
#### A Basic Manifest
|
||||
|
||||
The most common way to define a service's manifest is to place it in its own
|
||||
source target within the service's C++ client library. To combine the
|
||||
convenience of inline one-time initialization with the avoidance of static
|
||||
initializers, typically this means using a function-local static with
|
||||
`base::NoDestructor` and `service_manager::ManifestBuilder` as below. First the
|
||||
header:
|
||||
|
||||
``` cpp
|
||||
// src/services/storage/public/cpp/manifest.h
|
||||
|
||||
#include "services/service_manager/public/cpp/manifest.h"
|
||||
|
||||
namespace storage {
|
||||
|
||||
const service_manager::Manifest& GetManifest();
|
||||
|
||||
} // namespace storage
|
||||
```
|
||||
|
||||
And for the actual implementation:
|
||||
|
||||
``` cpp
|
||||
// src/services/storage/public/cpp/manifest.cc
|
||||
|
||||
#include "services/storage/public/cpp/manifest.h"
|
||||
|
||||
#include "base/no_destructor.h"
|
||||
#include "services/storage/public/mojom/constants.mojom.h"
|
||||
#include "services/service_manager/public/cpp/manifest_builder.h"
|
||||
|
||||
namespace storage {
|
||||
|
||||
const service_manager::Manifest& GetManifest() {
|
||||
static base::NoDestructor<service_manager::Manifest> manifest{
|
||||
service_manager::ManifestBuilder()
|
||||
.WithServiceName(mojom::kServiceName)
|
||||
.Build()};
|
||||
return *manifest;
|
||||
};
|
||||
|
||||
} // namespace storage
|
||||
```
|
||||
|
||||
Here we've specified only the **service name**, matching the constant defined
|
||||
in `constants.mojom` so that other services can easily locate us without a
|
||||
hard-coded string.
|
||||
|
||||
With this manifest definition there is no way for our service to reach other
|
||||
services, and there's no way for other services to reach us; this is because
|
||||
we neither **expose** nor **require** any capabilities, thus the Service Manager
|
||||
will always block any interface request from us or targeting us.
|
||||
|
||||
#### Exposing Interfaces
|
||||
|
||||
Let's expose an "allocator" capability that grants permission to bind a
|
||||
`BlockAllocator` pipe. We can augment the above manifest definition as follows:
|
||||
|
||||
``` cpp
|
||||
...
|
||||
#include "services/storage/public/mojom/block.mojom.h"
|
||||
...
|
||||
|
||||
...
|
||||
.WithServiceName(mojom::kServiceName)
|
||||
.ExposeCapability(
|
||||
mojom::kAllocatorCapability,
|
||||
service_manager::Manifest::InterfaceList<mojom::BlockAllocator>())
|
||||
.Build()
|
||||
...
|
||||
```
|
||||
|
||||
This declares the existence of an `"allocator"` capability exposed by our
|
||||
service, and specifies that granting a client this capability means granting it
|
||||
the privilege to send our service `storage.mojom.BlockAllocator` interface
|
||||
requests.
|
||||
|
||||
You can list as many interfaces as you like for each exposed capability, and
|
||||
multiple capabilities may list the same interface.
|
||||
|
||||
**NOTE**: You only need to expose an interface through a capability if you want
|
||||
other services to be able to be able to request it *through the Service
|
||||
Manager* (see [Connectors](#Connectors)) -- that is, if you handle requests for
|
||||
it in your `Service::OnBindInterface` implementation.
|
||||
|
||||
Contrast this with interfaces acquired transitively, like `Block` above. The
|
||||
Service Manager does not mediate the behavior of existing interface connections,
|
||||
so once a client has a `BlockAllocator` they can use `BlockAllocator.Allocate`
|
||||
to send as many `Block` requests as they like. Such requests go directly to
|
||||
the service-side implementation of `BlockAllocator` to which the pipe is bound,
|
||||
and so manifest contents are irrelevant to their behavior.
|
||||
|
||||
#### Getting Access to Interfaces
|
||||
|
||||
We don't need to add anything else to our `storage` manifest, but if another
|
||||
service wanted to enjoy access to our amazing storage block allocation
|
||||
facilities, they would need to declare in their manifest that they **require**
|
||||
our `"allocation"` capability. For ease of maintenance they would utilitize our
|
||||
publicly defined constants to do this. It's pretty straightforward:
|
||||
|
||||
``` cpp
|
||||
// src/services/some_other_pretty_cool_service/public/cpp/manifest.cc
|
||||
|
||||
... // Somewhere along the chain of ManifestBuilder calls...
|
||||
.RequireCapability(storage::mojom::kServiceName,
|
||||
storage::mojom::kAllocationCapability)
|
||||
...
|
||||
```
|
||||
|
||||
Now `some_other_pretty_cool_service` can use its [Connector](#Connectors) to ask
|
||||
the Service Manager for a `BlockAllocator` from us, like so:
|
||||
|
||||
``` cpp
|
||||
mojo::Remote<storage::mojom::BlockAllocator> allocator;
|
||||
connector->Connect(storage::mojom::kServiceName,
|
||||
allocator.BindNewPipeAndPassReceiver());
|
||||
|
||||
mojo::Remote<storage::mojom::Block> block;
|
||||
allocator->Allocate(42, block.BindNewPipeAndPassReceiver());
|
||||
|
||||
// etc..
|
||||
```
|
||||
|
||||
#### Other Manifest Elements
|
||||
|
||||
There are a handful of other optional elements in a `Manifest` structure which
|
||||
can affect how your service behaves at runtime. See the current
|
||||
[`Manifest`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/manifest.h)
|
||||
definition and comments as well as
|
||||
[`ManifestBuilder`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/manifest_builder.h)
|
||||
for the most complete and current information, but some of the more common
|
||||
properties specified by manifests are:
|
||||
|
||||
- **Display Name** - This is the string the Service Manager will use to name
|
||||
any new process created to run your service. This string would appear in the
|
||||
Windows Task Manager to identify the service process, for example.
|
||||
- **Options** - A few miscellaneous options are stuffed into a `ManifestOptions`
|
||||
field. These include sandbox type (see
|
||||
[Sandbox Configurations](#Sandbox-Configurations)),
|
||||
[instance sharing policy](#Instance-Sharing), and various behavioral flags to
|
||||
control a few [special capabilities](#Additional-Capabilities).
|
||||
- **Preloaded Files** - On Android and Linux platforms, the Service Manager can
|
||||
open specified files on the service's behalf and pass the corresponding open
|
||||
file descriptor(s) to each new service process on launch.
|
||||
- **Packaged Services** - A service may declare that it **packages** another
|
||||
service by including a copy of that service's own manifest. See
|
||||
[Packaging](#Packaging) for details.
|
||||
|
||||
### Running the Service
|
||||
|
||||
Hooking the service up so that it can be run in a production environment is
|
||||
actually outside the scope of this document at the moment, only because it still
|
||||
depends heavily on the environment in which the Service Manager is embedded. For
|
||||
now, if you want to get your great little service hooked up in Chromium for
|
||||
example, you should check out the sections on this in the very Chromium-centric
|
||||
[Intro to Mojo & Services](/docs/mojo_and_services.md#Hooking-Up-the-Service-Implementation)
|
||||
and/or
|
||||
[Servicifying Chromium Features](/docs/servicification.md#Putting-It-All-Together)
|
||||
documents.
|
||||
|
||||
For the sake of this document, we'll focus on running the service in test
|
||||
environments with the service both in-process and out-of-process.
|
||||
|
||||
### Testing
|
||||
|
||||
There are three primary approaches used when testing services, applied in
|
||||
varying combinations:
|
||||
|
||||
#### Standard Unit-testing
|
||||
This is ideal for covering details of your service's internal components and
|
||||
making sure they operate as expected. There is nothing special here regarding
|
||||
services. Code is code, you can unit-test it.
|
||||
|
||||
#### Out-of-process End-to-end Tests
|
||||
These are good for emulating a production environment as closely as possible,
|
||||
with your service implementation isolated in a separate process from the test
|
||||
(client) code.
|
||||
|
||||
The main drawback to this approach is that it limits your test's ability to poke
|
||||
at or observe internal service state, which can sometimes be useful in test
|
||||
environments (for *e.g.* faking out some behavior in a predictable manner). In
|
||||
general, supporting such controls means adding test-only interfaces to your
|
||||
service.
|
||||
|
||||
The
|
||||
[`TestServiceManager`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/test/test_service_manager.h)
|
||||
helper and
|
||||
[`service_executable`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/service_executable.gni)
|
||||
GN target type make this fairly easy to accomplish. You simply define a new
|
||||
entry point for your service:
|
||||
|
||||
``` cpp
|
||||
// src/services/storage/service_main.cc
|
||||
|
||||
#include "base/message_loop.h"
|
||||
#include "services/service_manager/public/cpp/service_executable/main.h"
|
||||
#include "services/storage/storage_service.h"
|
||||
|
||||
void ServiceMain(service_manager::ServiceRequest request) {
|
||||
base::SingleThreadTaskExecutor main_task_executor;
|
||||
storage::StorageService(std::move(request)).RunUntilTermination();
|
||||
}
|
||||
```
|
||||
|
||||
and a GN target for this:
|
||||
|
||||
``` python
|
||||
import "services/service_manager/public/cpp/service_executable.gni"
|
||||
|
||||
service_executable("storage") {
|
||||
sources = [
|
||||
"service_main.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
# The ":impl" target would be the target that defines our StorageService
|
||||
# implementation.
|
||||
":impl",
|
||||
"//base",
|
||||
"//services/service_manager/public/cpp",
|
||||
]
|
||||
}
|
||||
|
||||
test("whatever_unittests") {
|
||||
...
|
||||
|
||||
# Include the executable target as data_deps for your test target
|
||||
data_deps = [ ":storage" ]
|
||||
}
|
||||
```
|
||||
|
||||
And finally in your test code, use `TestServiceManager` to create a real
|
||||
Service Manager instance within your test environment, configured to know about
|
||||
your `storage` service.
|
||||
|
||||
`TestServiceManager` allows you to inject an artificial service instance to
|
||||
treat your test suite as an actual service instance. You can provide a manifest
|
||||
for your test to simulate requiring (or failing to require) various capabilities
|
||||
and get a `Connector` with which to reach your service-under-test. This looks
|
||||
something like:
|
||||
|
||||
``` cpp
|
||||
#include "services/service_manager/public/cpp/manifest_builder.h"
|
||||
#include "services/service_manager/public/cpp/test/test_service.h"
|
||||
#include "services/service_manager/public/cpp/test/test_service_manager.h"
|
||||
#include "services/storage/public/cpp/manifest.h"
|
||||
#include "services/storage/public/mojom/constants.mojom.h"
|
||||
#include "services/storage/public/mojom/block.mojom.h"
|
||||
...
|
||||
|
||||
TEST(StorageServiceTest, AllocateBlock) {
|
||||
const char kTestServiceName[] = "my_inconsequentially_named_test_service";
|
||||
service_manager::TestServiceManager service_manager(
|
||||
// Make sure the Service Manager knows about the storage service.
|
||||
{storage::GetManifest,
|
||||
|
||||
// Also make sure it has a manifest for our test service, which this
|
||||
// test will effectively act as an instance of.
|
||||
service_manager::ManifestBuilder()
|
||||
.WithServiceName(kTestServiceName)
|
||||
.RequireCapability(storage::mojom::kServiceName,
|
||||
storage::mojom::kAllocationCapability)
|
||||
.Build()});
|
||||
service_manager::TestService test_service(
|
||||
service_manager.RegisterTestInstance(kTestServiceName));
|
||||
|
||||
mojo::Remote<storage::mojom::BlockAllocator> allocator;
|
||||
|
||||
// This Connector belongs to the test service instance and can reach the
|
||||
// storage service through the Service Manager by virtue of the required
|
||||
// capability above.
|
||||
test_service.connector()->Connect(storage::mojom::kServiceName,
|
||||
allocator.BindNewPipeAndPassReceiver());
|
||||
|
||||
// Verify that we can request a small block of storage.
|
||||
mojo::Remote<storage::mojom::Block> block;
|
||||
allocator->Allocate(64, block.BindNewPipeAndPassReceiver());
|
||||
|
||||
// Do some stuff with the block, etc...
|
||||
}
|
||||
```
|
||||
|
||||
#### In-Process Service API Tests
|
||||
|
||||
Sometimes you want to poke at your service primarily through its client API,
|
||||
but you also want to be able to -- either for convenience or out of necessity --
|
||||
observe or manipulate its internal state within the test code. Running the
|
||||
service in-process is ideal in this case, and in that case there's not much use
|
||||
in involving the Service Manager or dealing with manifests.
|
||||
|
||||
Instead you can use a
|
||||
[`TestConnectorFactory`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/test/test_connector_factory.h)
|
||||
to give yourself a working `Connector` object which routes interface requests
|
||||
directly to specific service instances which you wire up directly. For a quick
|
||||
example, suppose we had some client library helper function for allocating a
|
||||
block of storage when given a `Connector`:
|
||||
|
||||
``` cpp
|
||||
// src/services/storage/public/cpp/allocate_block.h
|
||||
|
||||
namespace storage {
|
||||
|
||||
// This helper function can be used by any service which is granted the
|
||||
// |kAllocationCapability| capability.
|
||||
mojo::Remote<mojom::Block> AllocateBlock(service_manager::Connector* connector,
|
||||
uint64_t size) {
|
||||
mojo::Remote<mojom::BlockAllocator> allocator;
|
||||
connector->Connect(mojom::kServiceName, allocator.BindNewPipeAndPassReceiver());
|
||||
|
||||
mojo::Remote<mojom::Block> block;
|
||||
allocator->Allocate(size, block.BindNewPipeAndPassReceiver());
|
||||
return block;
|
||||
}
|
||||
|
||||
} // namespace storage
|
||||
```
|
||||
|
||||
Our test could look something like:
|
||||
|
||||
``` cpp
|
||||
TEST(StorageTest, AllocateBlock) {
|
||||
service_manager::TestConnectorFactory test_connector_factory;
|
||||
storage::StorageService service(
|
||||
test_connector_factory.RegisterInstance(storage::mojom::kServiceName));
|
||||
|
||||
constexpr uint64_t kTestBlockSize = 64;
|
||||
mojo::Remote<storage::mojom::Block> block = storage::AllocateBlock(
|
||||
test_connector_factory.GetDefaultConnector(), kTestBlockSize);
|
||||
block.FlushForTesting();
|
||||
|
||||
// Verify that we have the expected number of bytes allocated within the
|
||||
// service implementation.
|
||||
EXPECT_EQ(kTestBlockSize, service.GetTotalAllocationSizeForTesting());
|
||||
}
|
||||
```
|
||||
|
||||
### Connectors
|
||||
|
||||
While the
|
||||
[`Service`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/service.h)
|
||||
interface is what the Service Manager uses to drive a service instance's
|
||||
behavior, a
|
||||
[`Connector`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/connector.h)
|
||||
is what the service instance uses to send requests to the Service Manager. This
|
||||
interface is connected when your instance is launched, and `ServiceBinding`
|
||||
maintains and
|
||||
[exposes](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/service_binding.h?rcl=887b934e0d979f3da81c41cadc396b4ef587257a&l=66)
|
||||
it on your behalf.
|
||||
|
||||
#### Sending Interface Receivers
|
||||
|
||||
By far the most common and useful method on `Connector` is `Connect`,
|
||||
which allows your service to send an interface receiver to another service in the
|
||||
system, configuration permitting.
|
||||
|
||||
Supposing the `storage` service actually depended on an even lower-level storage
|
||||
service to get at its disk, you could imagine its block allocation code doing
|
||||
something like:
|
||||
|
||||
``` cpp
|
||||
mojo::Remote<real_storage::mojom::ReallyRealStorage> storage;
|
||||
service_binding_.GetConnector()->Connect(
|
||||
real_storage::mojom::kServiceName, storage.BindNewPipeAndPassReceiver());
|
||||
storage->AllocateBytes(...);
|
||||
```
|
||||
|
||||
Note that the first argument to this particular overload of `Connect` is
|
||||
a string, but the more generalized form of `Connect` takes a
|
||||
`ServiceFilter`. See more about these in the section on
|
||||
[Service Filters](#Service-Filters).
|
||||
|
||||
#### Registering Service Instances
|
||||
|
||||
One of the superpowers services can be granted is the ability to forcibly inject
|
||||
new service instances into the Service Manager's universe. This is done via
|
||||
[`Connector::ServiceInstance`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/connector.h?rcl=ec509adfa3ac85fab3cd51422b8aaf9cbb6b43cb&l=108) and is still
|
||||
used pretty heavily by Chromium's browser process. Most services don't need to
|
||||
touch this API.
|
||||
|
||||
#### Usage in Multithreaded Environments
|
||||
|
||||
Connectors are **not** thread-safe, but they do support **cloning**. There are
|
||||
two useful ways you can associate a new Connector with an existing one on a
|
||||
different thread.
|
||||
|
||||
You can `Clone` the `Connector` on its own thread and then pass the clone to
|
||||
another thread:
|
||||
|
||||
``` cpp
|
||||
std::unique_ptr<service_manager::Connector> new_connector = connector->Clone();
|
||||
base::PostTask(...[elsewhere]...,
|
||||
base::BindOnce(..., std::move(new_connector)));
|
||||
```
|
||||
|
||||
Or you can fabricate a brand new `Connector` right from where you're standing,
|
||||
and asynchronously associate it with one on another thread:
|
||||
|
||||
``` cpp
|
||||
mojo::PendingReceiver<service_manager::mojom::Connector> receiver;
|
||||
std::unique_ptr<service_manager::Connector> new_connector =
|
||||
service_manager::Connector::Create(&receiver);
|
||||
|
||||
// |new_connector| can be used to start issuing calls immediately, despite not
|
||||
// yet being associated with the establshed Connector. The calls will queue as
|
||||
// long as necessary.
|
||||
|
||||
base::PostTask(
|
||||
...[over to the correct thread]...,
|
||||
base::BindOnce([](
|
||||
mojo::PendingReceiver<service_manager::Connector> receiver) {
|
||||
service_manager::Connector* connector = GetMyConnectorForThisThread();
|
||||
connector->BindConnectorReceiver(std::move(receiver));
|
||||
}));
|
||||
```
|
||||
|
||||
### Identity
|
||||
|
||||
Every service instance started by the Service Manager is assigned a globally
|
||||
unique (across space *and* time) identity, encapsulated by the
|
||||
[`Identity`](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/identity.h)
|
||||
type. This value is communicated to the service and retained and
|
||||
[exposed](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/service_binding.h?rcl=b8bc0ab281f2cb5cd567dc994692c6022845fb89&l=62)
|
||||
by `ServiceBinding` immediately before `Service::OnStart` is invoked.
|
||||
|
||||
There are *four* components to an `Identity`:
|
||||
|
||||
- Service name
|
||||
- Instance ID
|
||||
- Instance group ID
|
||||
- Globally unique ID
|
||||
|
||||
You're already quite familiar with the **service name**: this is whatever the
|
||||
service declared in its manifest, *e.g.*, `"storage"`.
|
||||
|
||||
#### Instance ID
|
||||
|
||||
**Instance ID** is a `base::Token` qualifier which is simply used to
|
||||
differentiate multiple instances of the service if multiple instances are
|
||||
desired for whatever arbitrary reason. By default instances get an instance ID
|
||||
of zero when started unless a connecting client *explicitly* requests a specific
|
||||
instance ID. Doing so requires a special manifest-declared capability covered by
|
||||
[Additional Capabilities](#Additional-Capabilities).
|
||||
|
||||
*** aside
|
||||
A good example of how instance ID can be useful: the `"unzip"` service in
|
||||
Chrome is used to safely unpack untrusted Chrome extensions (CRX) archives, but
|
||||
we don't want multiple extensions being unpacked by the same process. To support
|
||||
this, Chrome generates a random `base::Token` for the instance ID it uses when
|
||||
connecting to the `"unzip"` service, and this elicits the creation of a new
|
||||
service instance in a new isolated process for each such connection. See
|
||||
[Service Filters](#Service-Filters) for how this can be done.
|
||||
***
|
||||
|
||||
#### Instance Group ID
|
||||
|
||||
All created service instances implicitly belong to an **instance group**, which
|
||||
is also identified by a `base::Token`. Unless either specially privileged by
|
||||
[Additional Capabilities](#Additional-Capabilities), or the target service is
|
||||
a [singleton or shared across groups](#Instance-Sharing), the service sending out
|
||||
an interface request can only reach other service instances in the same instance
|
||||
group. See [Instance Groups](#Instance-Groups) for more information.
|
||||
|
||||
#### Globally Unique ID
|
||||
|
||||
Finally, the **globally unique ID** is a cryptographically secure, unguessably
|
||||
random `base::Token` value which can be considered unique across all time and
|
||||
space. This can never be controlled by an instance or even by a highly
|
||||
privileged service, and its sole purpose is to ensure that `Identity` itself
|
||||
can be treated as unique across time and space. See
|
||||
[Service Filters](#Service-Filters) and
|
||||
[Observing Service Instances](#Observing-Service-Instances) for why this
|
||||
property of uniqueness is useful and sometimes necessary.
|
||||
|
||||
### Instance Sharing
|
||||
|
||||
Assuming the Service Manager has decided to allow an interface request due to
|
||||
sufficient capability requirements, it must consider a number of factors to
|
||||
decide where exactly to route the request. The first factor is the **instance
|
||||
sharing policy** of the target service, declared in its manifest. There are
|
||||
three supported policies:
|
||||
|
||||
- **No sharing** - This means the precise identity of the target instance
|
||||
depends on both the instance ID provided by the request's `ServiceFilter`,
|
||||
as well as the instance group either provided by the `ServiceFilter` or
|
||||
inherited from the source instance's group.
|
||||
- **Shared across groups** - This means the precise identity of the target
|
||||
instance still depends on the instance ID provided by the request's
|
||||
`ServiceFilter`, but the instance group of both the `ServiceFilter` and the
|
||||
source instance are completely ignored.
|
||||
- **Singleton** - This means there can be only one instance of the service at
|
||||
a time, no matter what. Instance ID and group are always ignored when
|
||||
connecting to the service.
|
||||
|
||||
Based on one of the policies above, the Service Manager determines whether or
|
||||
not an existing service instance matches the parameters specified by the given
|
||||
`ServiceFilter` in conjunction with the source instance's own identity. If so,
|
||||
that Service Manager will forward the interface request to that instance via
|
||||
`Service::OnBindInterface`. Otherwise, it will spawn a new instance which
|
||||
sufficiently matches the constraints, and it will forward the request to that
|
||||
new instance.
|
||||
|
||||
### Instance Groups
|
||||
|
||||
Service instances are organized into **instance groups**. These are arbitrary
|
||||
partitions of instances which can be used by the host application to impose
|
||||
various kinds of security boundaries.
|
||||
|
||||
Most services in the system do not have the privilege of specifying the
|
||||
instance group they want to connect into when passing a `ServiceFilter` to
|
||||
`Connector::Connect` (see
|
||||
[Additional Capabilities](#Additional-Capabilities)). As such, most
|
||||
`Connect` calls implicitly inherit the group ID of the caller and only
|
||||
cross outside of the caller's instance group when targeting a service which
|
||||
adopts either a singleton or shared-across-groups
|
||||
[sharing policy](#Instance-Sharing) in its manifest.
|
||||
|
||||
Singleton and shared-across-groups services are themselves always run in their
|
||||
own isolated groups.
|
||||
|
||||
### Service Filters
|
||||
|
||||
The most common form of `Connect` calls passes a simple string as the
|
||||
first argument. This is essentially telling the Service Manager that the caller
|
||||
doesn't care about any details regarding the target instance's identity -- it
|
||||
only cares about talking to *some* instance of the named service.
|
||||
|
||||
When a client *does* care about other details, they can explicitly construct and
|
||||
pass a `ServiceFilter` object, which essentially provides some subset of the
|
||||
desired target instance's total `Identity`.
|
||||
|
||||
Specifying an instance group or instance ID in a `ServiceFilter` requires a
|
||||
service to declare [additional capabilities](#Additional-Capabilities) in its
|
||||
manifest options.
|
||||
|
||||
A `ServiceFilter` can also wrap a complete `Identity` value, including the
|
||||
globally unique ID. This filter always *only* matches a specific instance unique
|
||||
in space and time. So if the identified instance has died and been replaced by
|
||||
a new instance with the same service name, same instance ID, and same instance
|
||||
group, the request will still *fail*, because the globally unique ID component
|
||||
will *never* match this or any future instance.
|
||||
|
||||
One useful property of targeting a specific `Identity` is that the client can
|
||||
connect without any risk of eliciting new target instance creation: either
|
||||
the target exists and the request can be routed, or the target doesn't exist
|
||||
and the request will be dropped.
|
||||
|
||||
### Additional Capabilities
|
||||
|
||||
Service manifests can use `ManifestOptionsBuilder` to set a few additional
|
||||
boolean options controlling their Service Manager privileges:
|
||||
|
||||
- `CanRegisterOtherServiceInstances` - If this is `true` the service can call
|
||||
`RegisterServiceInstance` on its `Connector` to forcibly introduce new service
|
||||
instances into the environment.
|
||||
- `CanConnectToInstancesWithAnyId` - If this is `true` the service can specify
|
||||
an instance ID in any `ServiceFilter` it passes to `Connect`.
|
||||
- `CanConnectToInstancesInAnyGroup` - If this is `true` the service can specify
|
||||
an instance group ID in any `ServiceFilter` it passes to `Connect`.
|
||||
|
||||
### Packaging
|
||||
|
||||
A service can declare that it **packages** another service by
|
||||
[nesting](https://cs.chromium.org/chromium/src/services/service_manager/public/cpp/manifest_builder.h?rcl=7839843db1ccdf13c3f1b8cb90a763989dde83a8&l=87) that
|
||||
service's manifest within its own.
|
||||
|
||||
This signals to the Service Manager that it should defer to the packaging
|
||||
service when it needs a new instance of the packaged service. For example, if
|
||||
we offered up the manifest:
|
||||
|
||||
``` cpp
|
||||
service_manager::ManifestBuilder()
|
||||
.WithServiceName("fruit_vendor")
|
||||
...
|
||||
.PackageService(service_manager::ManifestBuilder()
|
||||
.WithServiceName("banana_stand")
|
||||
.Build())
|
||||
.Build()
|
||||
```
|
||||
|
||||
And someone wanted to connect to a new instance of the `"banana_stand"` service
|
||||
(there's always money in the banana stand), the Service Manager would ask an
|
||||
appropriate `"fruit_vendor"` instance to do this on its behalf.
|
||||
|
||||
*** aside
|
||||
NOTE: If an appropriate instance of `"fruit_vendor"` wasn't already running --
|
||||
as determined by the rules described in [Instance Sharing](#Instance-Sharing)
|
||||
above -- one would first be spawned by the Service Manager.
|
||||
***
|
||||
|
||||
In order to support this operation, the `fruit_vendor` must expose a capability
|
||||
named exactly `"service_manager:service_factory"` which includes the
|
||||
`"service_manager.mojom.ServiceFactory"` interface. Then it must handle requests
|
||||
for the `service_manager.mojom.ServiceFactory` interface in its implementation
|
||||
of `Service::OnBindInterface`. The implementation of `ServiceFactory` provided
|
||||
by the service must then handle the `CreateService` that will be sent by
|
||||
the Service Manager. This call will include the name of the service and the
|
||||
`ServiceRequest` the new service instance will need to bind.
|
||||
|
||||
*** aside
|
||||
NOTE: It is this complicated for historical reasons. Expect it to be less
|
||||
complicated soon.
|
||||
***
|
||||
|
||||
Services can use this for example if, in certain runtime environments, they want
|
||||
to share their process with another service.
|
||||
|
||||
*** aside
|
||||
FUN FACT: This is actually how Chromium manages *all* services today, because
|
||||
the Content layer still owns much of the production-ready process launching
|
||||
logic. We have a singleton `content_packaged_services` service which packages
|
||||
nearly all other registered services in the system, and so the Service Manager
|
||||
defers (via `ServiceFactory`) nearly all service instance creation operations
|
||||
to Content.
|
||||
***
|
||||
|
||||
### Sandbox Configurations
|
||||
|
||||
Service manifests support specifying a fixed sandbox configuration for the
|
||||
service to be launched with when run out-of-process. Currently these values
|
||||
are strings which must match one of the defined constants
|
||||
[here](https://cs.chromium.org/chromium/src/services/service_manager/sandbox/switches.cc?rcl=2e6a3bddac0aff89c5ff415e9c1cd4da804280ef&l=23).
|
||||
|
||||
The most common and default value is `"utility"`, which is a restrictive sandbox
|
||||
configuration and generally a safe choice. For services which must run
|
||||
unsandboxed, use the value `"none"`. Use of other sandbox configurations should
|
||||
be done under the advisory of Chrome's security reviewers.
|
||||
|
||||
### Observing Service Instances
|
||||
|
||||
Services which require the `"service_manager:service_manager`" capability from
|
||||
the `"service_manager"` service can connect to the `"service_manager"` service
|
||||
to request a
|
||||
[`ServiceManager`](https://cs.chromium.org/chromium/src/services/service_manager/public/mojom/service_manager.mojom?rcl=765c18ee7c317535594ba37520a23c11f0cef008&l=82)
|
||||
interface. This can in turn be used to register a new
|
||||
[`ServiceManagerListener`](https://cs.chromium.org/chromium/src/services/service_manager/public/mojom/service_manager.mojom?rcl=765c18ee7c317535594ba37520a23c11f0cef008&l=44) to
|
||||
observe lifecycle events pertaining to all service instances hosted by the
|
||||
Service Manager.
|
||||
|
||||
There are several
|
||||
[examples](https://cs.chromium.org/search/?q=mojo::Binding%3Cservice_manager::mojom::ServiceManagerListener%3E&type=cs)
|
||||
of this throughout the tree.
|
||||
|
||||
## Additional Support
|
||||
|
||||
If this document was not helpful in some way, please post a message to your
|
||||
friendly
|
||||
[services-dev@chromium.org](https://groups.google.com/a/chromium.org/forum/#!forum/services-dev)
|
||||
mailing list.
|
||||
|
||||
Also don't forget to take a look at other
|
||||
[Mojo & Services](/docs/README.md#Mojo-Services) documentation in the tree.
|
Reference in New Issue
Block a user