This document assumes that the reader has read the high-level design of layered components, and presents strategies for realizing that design. Choosing Features to RefactorWe will aim to tackle simpler “leaf” features first. By tackling these features first, we can wrestle with the basic issues and establish basic structure/patterns in a simpler context. As we move to more complex features, the extra complexities that they introduce will thus be easier to isolate and focus on. Dealing with Upstreaming’s Incremental NatureFor the foreseeable future, Chrome for iOS will be in a state where some code is upstream and some code is downstream. This fact creates two challenges:
The first case will be handled by introducing an API that allows the downstream codebase to inject functionality into upstream features via embedder objects (precisely following the example of ContentClient and friends). The second case will be handled by introducing an API via which downstream iOS code consumes upstream Chromium code. This “consumer API” will live upstream in directories that are owned by iOS engineers together with unittests of the API that specify the expected semantics. Thus, downstream usage of these APIs will be visible to all Chromium engineers (likely there will be a policy of TBR’ing an API OWNER on a change of a consumer API implementation but getting a full review from an OWNER on a change of a consumer API definition). Refactoring a Feature
Approaches to Challenges Encountered During RefactoringLaying the Groundwork via a DriverMost features both receive incoming information from the content layer (e.g., IPC or notifications) and send out information to or query the content layer (e.g., sending IPC, asking BrowserContext if it is off the record). To enable abstracting these interactions, the general pattern is to introduce a FooDriver interface. This interface will live in the core code of the component, and an instance of it will be injected into this core code on instantiation. The content-based and iOS drivers of the component will each have a concrete subclass of the FooDriver interface. Abstracting WebContentsObserverYou can make the content-based FooDriver implementation be a WebContentsObserver. This class can contain references to the core classes that were previously themselves WebContentsObservers and call (potentially new) appropriate public APIs that those core classes expose in response to observing events on the given WebContents. The iOS FooDriver implementation, by contrast, can observe WebState (iOS's equivalent to WebContents). The content-based FooDriver implementation can itself be a WebContentsUserData, and can own the core classes that were previously themselves WebContentsUserData objects. Thus, the lifetime of these core classes will still be scoped to the lifetime of the WebContents. The iOS FooDriver implementation, by contrast, can be a WebStateUserData. Abstracting Listening to Notifications and IPC ReceptionThe approach to these is similar to that for WebContentsObserver: have the content-based FooDriver implementation listen for the notifications/IPC, and then invoke appropriate calls on the core classes (potentially creating new public APIs on these core classes in order to do so). Example of abstracting listening for notifications: https://codereview.chromium.org/17893010/ Example of abstracting IPC reception: https://codereview.chromium.org/17382007/ The iOS implementation of the feature will likely just call the new public APIs on the core classes directly. Abstracting IPC SendingExpose methods on the FooDriver interface that the core code can call where it was previously directly sending IPC. The content-based FooDriver implementation will implement these calls by sending IPC. The iOS FooDriver will implement these methods via an iOS-specific flow. General Strategies to Consider When Doing Refactoring
Case Studies / Examples to Follow |
For Developers > Design Documents >