java-bridge.md 15 KB

WebView Java Bridge (WebView#addJavascriptInterface())

[TOC]

Overview

This page explains ideas behind the Java ↔ JavaScript bridge implementation. This is to ensure that important use cases and scenarios, which must be preserved regardless of how the bridge is implemented, are captured. The need for this description arose while migrating the NPAPI-based implementation to a Gin-based implementation. Although a vast number of unit tests already existed, they still didn’t cover all important aspects of the bridge behavior and we had to add some new tests to ensure we are preserving compatibility.

The Gin implementation was introduced in Chromium M37 (initial Android Lollipop release), with the threading issue fixed in M39 (L MR1).

The API

An API for embedders is exposed on android.webkit.WebView class:

Important notes as defined by the API:

  • adding or removing an injected object is not reflected on the JavaScript side until the next page load;
  • only methods annotated as @JavascriptInterface are exposed to JavaScript code; Java object fields are never exposed;
  • methods of Java objects are invoked on a private, background thread of WebView; this effectively means, that the interaction originated by the page must be served entirely on the background thread, while the main application thread (browser UI thread) is blocked;

Argument and return values conversions are handled after Sun Live Connect 2 spec. In fact, there are lots of deviations from it (probably, to preserve compatibility with earlier WebView versions). What can pass the boundary between VMs is somewhat limited. This is what is allowed:

  • primitive values;
  • single-dimentional arrays;
  • «array-like» JavaScript objects (possessing «length» property, and also typed arrays from ES6);
  • previously injected Java objects (from JS to Java);
  • new Java objects (from Java to JS), those are «injected» into JavaScript as if one called addJavascriptInterface, but w/o providing a name; also, the lifecycle of such transient objects is different (see below).

Objects Lifecycle

The purpose of Java bridge is to establish interaction between two virtual machines (VMs): Java and JavaScript. Both VMs employ a similar approach to managing objects lifetime—VMs gather and dispose unreferenced objects during garbage collection (GC) cycles. The twist that Java bridge adds is that objects in one VM can now virtually reference (and prevent from being disposed) objects from another VM. Let us consider the following Java code:

// in Java
webView.addJavascriptInterface(new MyObject(), "myObject");

The instantiated MyObject is now being virtually hold by its JavaScript counterpart, and is not garbage-collected by Java VM despite the fact that there are no explicit references to it on the Java side. The MyObject instance is kept referenced until the action of the addJavascriptInterface call is cancelled by a call to removeJavascriptInterface:

// in Java
webView.removeJavascriptInterface("myObject");

A more interesting situation is with transient objects returned from methods of an injected Java object. Consider the following example:

// in Java
class MyObject {
    class Handler {
    }

    @JavascriptInterface
    public Object getHandler() { return new Handler(); }
}

Again, the object returned from getHandler method is not explicitly referenced on the Java side, albeit it should not be disposed until it is in use on the JavaScript side. The «in use» period is determined by the lifetime of the JavaScript interface object that has been implicitly created as a result of a call to getHandler from JavaScript. That means, the instance of Handler on the Java side should be kept alive during the period while the corresponding JavaScript interface object is still referenced:

// in JavaScript
{
   ...
   let handler = myObject.getHandler();
   ...
}

The following figure illustrates relationships between Java and JavaScript objects created in the previous examples:

relationship between Java and JavaScript
objects

Note that Java and JavaScript VMs are absolutely independent and unaware of each other’s existence. They can work in different processes and, in theory, even on different physical machines. Thus, the depicted references from JavaScript objects to Java objects are virtual—they don’t exist directly. Instead, it is the Java bridge who holds the Java objects for as long as it is needed. We would like to depict that, but first we need to consider the whole picture.

So far, we were thinking about Java bridge in abstract terms. But in fact, it is used in the context of a WebView-based application. The Java side of the bridge is tightly coupled to an instance of WebView class, while bridge’s JavaScript side is bound to a HTML rendering engine. This is further complicated by the facts that in the Chromium architecture renderers are isolated from their controlling entities, and that Chromium is mainly implemented in C++, but needs to interact with Android framework which is implemented in Java.

Thus, if we want to depict the architecture of Java bridge, we also need to include parts of the Chromium framework that are glued to Java bridge:

Java bridge architecture

The figure is now much scarier. Let’s figure out what is what here:

  • In Java VM (browser side): WebView is android.webkit.WebView class. It is exposed to the embedder and interacts with Chromium rendering machinery. WebView owns a retaining set (Set<Object>) that holds all injected objects to prevent their collection. Note that WebView class manages a C++ object called WebContents (in fact, the relationship is more complex, but these details are not relevant for us). As Java Bridge implementation is in C++, the retaining set is actually managed by the C++ side, but objects from the native side do not hold any strong references to it, as that would create a cyclic reference and will prevent the WebView instance from being collected.
  • On the C++ browser side: Here we have the aforementioned WebContents object which delegates Java Bridge-related requests to JavaBridgeHost. WebContents talks to the objects on the renderer side via Chromium’s IPC mechanism.
  • On the C++ renderer side: RenderFrame corresponds to a single HTML frame and it «owns» a JavaScript global context object (aka window). For each JavaScript interface object, a corresponding JavaBridgeObject instance is maintained. In Chromium terminology, this object is called «wrapper». In the Gin-based implementation, wrappers don’t hold strong references to their corresponding JavaScript interface objects, also to prevent memory leaks due to cycles of references. Wrappers receive a notification from the JavaScript VM after the corresponding JavaScript objects has been garbage-collected.

The diagram above misses one more important detail. WebView can load a complex HTML document consisting of several frames (typically inserted using