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, as it will likely make the contents of this document easier to digest.
Also see general Mojo & Services documentation for other introductory guides, API references, etc.
There are three big things you must decide when building and hooking up a shiny new service:
This section aims to help you understand and answer those questions.
Based on the service development guidelines, 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 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).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
.
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.
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. 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:
content_packaged_services
manifest directly, similar to these ones.RegisterInProcessService
here.out_of_process_services
like so and hook up your actual private Service
implementation exactly like the many examples here.For services which are are isolated per BrowserContext and which can be wired directly into Content:
content_browser
manifest directly, similar to these ones.For services which are not isolated per BrowserContext but which can not be wired directly into Content:
content_packaged_services
manifest overlay similar to these onesChromeContentBrowserClient::HandleServiceRequest
hereChromeContentBrowserClient::RegisterOutOfProcessServices
like the examples here and hook up your Service
implementation in ChromeContentUtilityClient::HandleServiceRequest
like the ones here.For services which are isolated per BrowserContext but which can not be wired directly into Content:
content_browser
manifest overlay similar to these onesProfileImpl::HandleServiceRequest
hereContentBrowserClient
and ContentUtilityClient
APIs that all embedders can implement. Mimicking what Chrome does should be sufficient for any embedder.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:
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:
These all have tradeoffs:
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.
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 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. (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.
Services are supported on iOS, with the usage model in //ios/web being very close to the usage model in //content. More specifically:
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. In particular, search for the mentions of iOS within the doc.
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.
DocumentInterfaceBroker
approach to exposing interfaces to documents. New JS mocking support is in development for this.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:
To meet this challenge, you have two options:
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, the policy that we have adopted for this challenge is the following:
CURRENT
AFTER SERVICIFYING THE FEATURE IN QUESTION
Notably, from the renderer's POV essentially nothing changes here.
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.
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:
See this thread for more discussion of this issue.
If this document was not helpful in some way, please post a message to your friendly local chromium-mojo@chromium.org or services-dev@chromium.org mailing list.