How to Write Cross-Platform UI (Native components)

Chrome runs on many platforms such as Desktop (Windows, Mac, Linux, and ChromeOS), Android, iOS, and VR. These platforms have different UIs, written in different languages. Chrome also has WebUI, which is browser UI implemented in the HTML content area. This can be considered another UI toolkit.

Separating out platform-independent business logic

For UI that appears on multiple platforms, it's best to share as much of the business logic (model behavior backing the UI) as possible. The platform-specific views should be as minimal and “dumb” as possible. One caveat: sharing everything may not be feasible if there are performance or latency concerns.

The cross-platform model will be in native C++ code, likely in the //components/foobar directory. Model layer code should handle the business logic, and be richly observable. Platform-specific views/controllers should observe the model, update the UI, and mutate the model as needed. This model should have tests.

Note that there can be model state that is applicable to only a subset of platforms. For instance, mobile devices can change device rotation or layout. On Mobile, the platform itself may provide extended text editing capabilities that don't exist on Desktop.

Lifetime Issues

Different platforms may have different UI lifetime semantics, so it's helpful to keep the business logic stateless or with a flexible lifetime model. Android Java object lifetimes are well-defined, but has gotchas for cross-platform code. For instance: the Java side of Android is available immediately on startup, but the C++ side needs to wait for the native library to be loaded. Having a native object “own” a garbage-collected Java object or vice versa can also be tricky.

  1. Pure static functions are the easiest, if this works for your use case. In addition to being easy to bridge to Java, these are also easy to unit test, since they don't require setting up state.

    You can usually use pure static functions for simpler cases that don't involve state. These are easy to bridge to Java. See UrlFormatter for an example of this.

  2. For objects that are associated with a specific Profile, use a KeyedService instance with BrowserContextKeyedServiceFactory. If your object depends on other KeyedService instances, there's a strong chance your object should also be a KeyedService.

    These are also pretty easy to bridge to Java. In most cases, the Java code will need to pass a Profile object, so that the native KeyedService can be fetched.

    A neat trick is that the Java code can be all-static, even if the native code isn't, if every method also includes a Profile parameter.

  3. Objects tied to WebContents lifetimes are also commonly used, and can be implemented using a WebContentsObserver and WebContentsUserData.

  4. For lifetimes tied to UI instances, or other custom lifetimes, you will need to do custom lifetime management. For Java bridging advice, see the JNI documentation for details.

More Advice on Bridging to Java

Bridging a code module to Java will have three parts:

  1. components/omnibox/browser/foobar.h/cc - Cross-platform code you already wrote and have unit tests for.

  2. chrome/browser/android/omnibox/foo_feature_android.h/cc - C++ side of the bridge. You might only need the cc file.

  3. chrome/android/java/src/org/chromium/chrome/browser/omnibox/FooFeature.java

    • Java side of the bridge.

See the JNI README for more details.