A talk was recently given on some of the same material as this page, and it may serve as a good high-level overview before diving into the documentation. See the "Life of a Browser Component" file linked to at the bottom of this page for just the slides, or watch the video of the presentation.
We’ve started having multiple top-level applications. For one of these (Chrome for Android), the necessary approach was to #ifdef in //chrome, but we want to avoid this for future top-level applications.
The //components layer is a place for browser features that embedders of //content may want to add. Most of the components currently there are features that previously lived in //chrome but were extracted so that they could be reused by other top-level applications such as //android_webview.
New features that may get used by more than one top-level application should be written as components. For top-level applications other than Chrome for Android, features they wish to reuse should first be extracted into //components. This document is a cookbook -type guide for how to create a new component, and how to extract an existing feature from //chrome into a component.
Creating a new component is straightforward. Follow the rules in the design document, and the examples already in the //components directory. TL;DR version:
These are the typical steps to extract a feature:
For each component-to-be that makes up the feature, write a strict DEPS file as if it were already a component
Reduce the set of temporarily-allowed dependencies to zero
Move the code to its //components/xyz directory
Fix up .gypi files and add export declarations to build it as a component
Please feel free to add new sections, add clarifications and examples to this section, etc. If you don't have edit rights to the site, feel free to add comments at the bottom of the page with additional recipes.
Dependency inversion is the most fundamental approach to breaking dependencies. This is the pattern where you switch from object Foo depending concretely on object Bar, and instead object Foo defines an interface FooDelegate that it needs fulfilled by any embedder; this interface is then implemented by Bar, which passes a FooDelegate pointer to itself or its delegate implementation to Foo.
By using this pattern, it is easy to push knowledge of minor dependencies out of your component, and possible to remove knowledge of some larger dependencies (but see the next section).
Sometimes you’ll find that the feature you want to extract has a fairly big dependency on a fairly big chunk of code. Using dependency inversion (see above) would result in a large delegate interface or even multiple large delegate interfaces.
In these cases, you’ve probably discovered that the component you want isn’t a “leaf node,” whereas its dependency may be. In these cases you probably want to componentize this dependency first. Exceptions to this might be where the dependency would be fulfilled in entirely different ways, rather than in one common way, by different embedders; if this is the case then a large delegate interface (or set of delegates) may still be appropriate.
It’s very common in //chrome that an “everything” object such as Profile is passed in to initialize an object or a subsystem, which then turns around and retrieves just a couple of more fundamental objects from the Profile.
We’ve seen this many times, e.g. a Profile being passed and the only things retrieved from it being the PrefService associated with the Profile, and the SequencedTaskRunner for the IO thread.
In cases like this, you should change the code to pass the most fundamental objects possible, rather than passing more complex “everything” or “bag of stuff” objects. See also Law of Demeter.
Sometimes, you will encounter an object that brings in a lot of dependencies, but at the same time seems to have functionality that is core to the component. It might e.g. be an object that has some internal state machine that the component needs to use, and also initiates or participates in a bunch of UI interactions.
In cases like this, consider whether the object needs to be split into multiple objects, where one is the core business logic (which stays with the component) and another is the part that brings in all the dependencies (this could stay with the embedder).
It's worth noting that the programming interfaces that result from the types of dependency-breaking operations listed above are not always optimal, in fact they are often not as nice as what would have resulted from a feature being written as a component from the start. Once you've finished breaking dependencies, it can certainly be worth going back, taking a fresh look at the interfaces that resulted, and cleaning them up.