blob: d24dcd3686230edb56e127646774d89c30ae051a [file] [log] [blame] [view]
# Servicifying Chromium Features
[TOC]
## Overview
Much to the dismay of Chromium developers, practicing linguists, and keyboard
operators everywhere, the term **servicificificification** [sic] has been
egregiously smuggled into the Chromium parlance.
Lots of Chromium code is contained in reasonably well-isolated component
libraries with some occasionally fuzzy boundaries and often a surprising number
of gnarly runtime interdependencies among a complex graph of components. Y
implements one of Z's delegate interfaces, while X implements one of Y's
delegate interfaces, and now it's possible for some ridiculous bug to creep in
where W calls into Z at the wrong time and causes a crash in X. Yikes.
Servicification embodies the ongoing process of **servicifying** Chromium
features and subsystems, or refactoring these collections of library code into
services with well-defined public API boundaries and very strong runtime
isolation via Mojo interfaces.
The primary goals are to improve maintainability and extensibility of the system
over time, while also allowing for more flexible runtime configuration. For
example, with the Network Service in place we can now run the entire network
stack either inside or outside of the browser process with the flip of a
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.
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.
### Where in the Tree?
Based on the
[service development guidelines](/services/README.md), any service which could
be reasonably justified as a core system service in a hypothetical,
well-designed operating system may belong in the top-level `//services`
directory. If that sounds super hand-wavy and unclear, that's because it is!
There isn't really a great universal policy here, so when in doubt, contact your
favorite local
[services-dev@chromium.org](https://groups.google.com/a/chromium.org/forum#!forum/services-dev)
mailing list and start a friendly discussion.
Other common places where developers place services, and why:
- `//components/services` for services which haven't yet made the cut for
`//services` but which are either used by Content directly or by multiple
Content embedders.
- `//chrome/services` for services which are used exclusively within Chrome and
not shared with other Content embedders.
- `//chromeos/services` for services which are used on Chrome OS by more than
just Chrome itself (for example, if the `ash` service must also connect to
them for use in system UI).
### Inside Content or Not?
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.
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.
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`.
### Per-BrowserContext or Not?
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).
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.
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.
### Putting It All Together
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.
You're not going to believe it, but this section was written *just for YOU*.
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.
***
## Incremental Servicification
For large Chromium features it is not feasible to convert an entire subsystem
to a service all at once. As a result, it may be necessary for the subsystem
to spend a considerable amount of time (weeks or months) split between the old
implementation and your beautiful, sparkling new service implementation.
In creating your service, you likely have two goals:
- Making the service available to its consumers
- Making the service self-contained
Those two goals are not the same, and to some extent are at tension:
- To satisfy the first, you need to build out the API surface of the service to
a sufficient degree for the anticipated use cases.
- To satisfy the second, you need to convert all clients of the code that you
are servicifying to instead use the service, and then fold that code into the
internal implementation of the service.
Whatever your goals, you will need to proceed incrementally if your project is
at all non-trivial (as they basically all are given the nature of the effort).
You should explicitly decide what your approach to incremental bringup and
conversion will be. Here are some approaches that have been taken for various
services:
- Build out your service depending directly on existing code,
convert the clients of that code 1-by-1, and fold the existing code into the
service implementation when complete ([Identity Service](https://docs.google.com/document/d/1EPLEJTZewjiShBemNP5Zyk3b_9sgdbrZlXn7j1fubW0/edit)).
- Build out the service with new code and make the existing code
into a client library of the service. In that fashion, all consumers of the
existing code get converted transparently ([Preferences Service](https://docs.google.com/document/d/1JU8QUWxMEXWMqgkvFUumKSxr7Z-nfq0YvreSJTkMVmU/edit#heading=h.19gc5b5u3e3x)).
- Build out the new service piece-by-piece by picking a given
bite-size piece of functionality and entirely servicifying that functionality
([Device Service](https://docs.google.com/document/d/1_1Vt4ShJCiM3fin-leaZx00-FoIPisOr8kwAKsg-Des/edit#heading=h.c3qzrjr1sqn7)).
These all have tradeoffs:
- The first lets you incrementally validate your API and implementation, but
leaves the service depending on external code for a long period of time.
- The second can create a self-contained service more quickly, but leaves
all the existing clients in place as potential cleanup work.
- The third ensures that you're being honest as you go, but delays having
the breadth of the service API up and going.
Which makes sense depends both on the nature of the existing code and on
the priorities for doing the servicification. The first two enable making the
service available for new use cases sooner at the cost of leaving legacy code in
place longer, while the last is most suitable when you want to be very exacting
about doing the servicification cleanly as you go.
## Platform-Specific Issues: Android
As you servicify code running on Android, you might find that you need to port
interfaces that are served in Java. Here is an
[example CL](https://codereview.chromium.org/2643713002) that gives a basic
pattern to follow in doing this.
You also might need to register JNI in your service. That is simple to set
up, as illustrated in [this CL](https://codereview.chromium.org/2690963002).
(Note that that CL is doing more than *just* enabling the Device Service to
register JNI; you should take the register_jni.cc file added there as your
starting point to examine the pattern to follow).
Finally, it is possible that your feature will have coupling to UI process state
(e.g., the Activity) via Android system APIs. To handle this challenging
issue, see the section on [Coupling to UI](#Coupling-to-UI).
## Platform-Specific Issues: iOS
*** aside
**WARNING:** Some of this content is obsolete and needs to be updated. When in
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.
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
supporting services on iOS, see the high-level
[servicification design doc](https://docs.google.com/document/d/15I7sQyQo6zsqXVNAlVd520tdGaS8FCicZHrN0yRu-oU/edit).
In particular, search for the mentions of iOS within the doc.
## Client-Specific Issues
#### Mocking Interface Impls in JS
It is a common pattern in Blink's web tests to mock a remote Mojo interface
in JS so that native Blink code requests interfaces from the test JS rather
than whatever would normally service them in the browser process.
The current way to set up that sort of thing looks like
[this](https://cs.chromium.org/chromium/src/third_party/blink/web_tests/battery-status/resources/mock-battery-monitor.js?rcl=be6e0001855f7f1cfc26205d0ff5a2b5b324fcbd&l=19).
*** aside
**NOTE:** The above approach to mocking in JS no longer applies when using
the new recommended `DocumentInterfaceBroker` approach to exposing interfaces
to documents. New JS mocking support is in development for this.
***
#### Feature Impls That Depend on Blink Headers
In the course of servicifying a feature that has Blink as a client, you might
encounter cases where the feature implementation has dependencies on Blink
public headers (e.g., defining POD structs that are used both by the client and
by the feature implementation). These dependencies pose a challenge:
- Services should not depend on Blink, as this is a dependency inversion (Blink
is a client of services).
- However, Blink is very careful about accepting dependencies from Chromium.
To meet this challenge, you have two options:
1. Move the code in question from C++ to mojom (e.g., if it is simple structs).
2. Move the code into the service's C++ client library, being very explicit
about its usage by Blink. See
[this CL](https://codereview.chromium.org/2415083002) for a basic pattern to
follow.
#### Frame-Scoped Connections
You must think carefully about the scoping of the connection being made
from Blink. In particular, some feature requests are necessarily scoped to a
frame in the context of Blink (e.g., geolocation, where permission to access the
interface is origin-scoped). Servicifying these features is then challenging, as
Blink has no frame-scoped connection to arbitrary services (by design, as
arbitrary services have no knowledge of frames or even a notion of what a frame
is).
After a
[long discussion](https://groups.google.com/a/chromium.org/forum/#!topic/services-dev/CSnDUjthAuw),
the policy that we have adopted for this challenge is the following:
CURRENT
- The renderer makes a request through its frame-scoped connection to the
browser.
- The browser obtains the necessary permissions before directly servicing the
request.
AFTER SERVICIFYING THE FEATURE IN QUESTION
- The renderer makes a request through its frame-scoped connection to the
browser.
- The browser obtains the necessary permissions before forwarding the
request on to the underlying service that hosts the feature.
Notably, from the renderer's POV essentially nothing changes here.
## Strategies for Challenges to Decoupling from //content
### Coupling to UI
Some feature implementations have hard constraints on coupling to UI on various
platforms. An example is NFC on Android, which requires the Activity of the view
in which the requesting client is hosted in order to access the NFC platform
APIs. This coupling is at odds with the vision of servicification, which is to
make the service physically isolatable. However, when it occurs, we need to
accommodate it.
The high-level decision that we have reached is to scope the coupling to the
feature *and* platform in question (rather than e.g. introducing a
general-purpose FooServiceDelegate), in order to make it completely explicit
what requires the coupling and to avoid the coupling creeping in scope.
The basic strategy to support this coupling while still servicifying the feature
in question is to inject a mechanism of mapping from an opaque "context ID" to
the required context. The embedder (e.g., //content) maintains this map, and the
service makes use of it. The embedder also serves as an intermediary: it
provides a connection that is appropriately context-scoped to clients. When
clients request the feature in question, the embedder forwards the request on
along with the appropriate context ID. The service impl can then map that
context ID back to the needed context on-demand using the mapping functionality
injected into the service impl.
To make this more concrete, see
[this CL](https://codereview.chromium.org/2734943003).
### Shutdown of Singletons
You might find that your feature includes singletons that are shut down as part
of //content's shutdown process. As part of decoupling the feature
implementation entirely from //content, the shutdown of these singletons must be
either ported into your service or eliminated:
- In general, as Chromium is moving away from graceful shutdown, the first
question to analyze is: Do the singletons actually need to be shut down at
all?
- If you need to preserve shutdown of the singleton, the naive approach is to
move the shutdown of the singleton to the destructor of your service
- However, you should carefully examine when your service is destroyed compared
to when the previous code was executing, and ensure that any differences
introduced do not impact correctness.
See
[this thread](https://groups.google.com/a/chromium.org/forum/#!topic/services-dev/Y9FKZf9n1ls)
for more discussion of this issue.
## Additional Support
If this document was not helpful in some way, please post a message to your
friendly local
[chromium-mojo@chromium.org](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-mojo)
or
[services-dev@chromium.org](https://groups.google.com/a/chromium.org/forum/#!forum/services-dev)
mailing list.