blob: c4598528a28b026aee1a1c23c2eb350edfcc8f12 [file] [log] [blame] [view]
Shimi Zhangda40d0f2020-09-03 20:17:271# WebView Java Bridge (WebView#addJavascriptInterface())
2
3[TOC]
4
5## Overview
6
7This page explains ideas behind the Java ↔ JavaScript bridge
8implementation. This is to ensure that important use cases and scenarios, which
9must be preserved regardless of how the bridge is implemented, are captured. The
10need for this description arose while migrating the NPAPI-based implementation
11to a [Gin](/gin/)-based implementation. Although a vast number of unit tests
12already existed, they still didn't cover all important aspects of the bridge
13behavior and we had to add some new tests to ensure we are preserving
14compatibility.
15
Shimi Zhangda40d0f2020-09-03 20:17:2716## The API
17
18An API for embedders is exposed on
19[android.webkit.WebView](https://developer.android.com/reference/android/webkit/WebView.html)
20class:
21
22- [public void **addJavascriptInterface**(Object **object**, String
23 **name**)](https://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object,%20java.lang.String))
24 -- injects a Java **object** into a WebView under the given **name**;
25- [public void **removeJavascriptInterface**(String
26 **name**)](https://developer.android.com/reference/android/webkit/WebView.html#removeJavascriptInterface(java.lang.String))
27 -- removes an object previously injected under the **name**.
28
29Important notes as defined by the API:
30- adding or removing an injected object is not reflected on the JavaScript side
31 until the next page load;
32- only methods annotated as
33 [`@JavascriptInterface`](https://developer.android.com/reference/android/webkit/JavascriptInterface.html)
34 are exposed to JavaScript code; Java object fields are never exposed;
35- methods of Java objects are invoked on a private, background thread of
36 WebView; this effectively means, that the interaction originated by the page
37 must be served entirely on the background thread, while the main application
38 thread (browser UI thread) is blocked;
39
40Argument and return values conversions are handled after [Sun Live Connect 2
41spec](https://www.oracle.com/java/technologies/javase/liveconnect-docs.html). In
42fact, there are lots of deviations from it (probably, to preserve compatibility
43with earlier WebView versions). What can pass the boundary between VMs is
44somewhat limited. This is what is allowed:
45- primitive values;
46- single-dimentional arrays;
47- "array-like" JavaScript objects (possessing "length" property, and also typed
48 arrays from ES6);
49- previously injected Java objects (from JS to Java);
50- new Java objects (from Java to JS), those are "injected" into JavaScript as if
51 one called **addJavascriptInterface**, but w/o providing a name; also, the
52 lifecycle of such transient objects is different (see below).
53
54## Objects Lifecycle
55
56The purpose of Java bridge is to establish interaction between two virtual
57machines (VMs): Java and JavaScript. Both VMs employ a similar approach to
Nate Fischerb84a88e2022-03-10 17:11:0058managing objects lifetime—VMs gather and dispose unreferenced objects during
Shimi Zhangda40d0f2020-09-03 20:17:2759garbage collection (GC) cycles. The twist that Java bridge adds is that objects
60in one VM can now virtually reference (and prevent from being disposed) objects
61from another VM. Let us consider the following Java code:
62
63```Java
64// in Java
65webView.addJavascriptInterface(new MyObject(), "myObject");
66```
67
68The instantiated MyObject is now being virtually hold by its JavaScript
69counterpart, and is not garbage-collected by Java VM despite the fact that there
70are no explicit references to it on the Java side. The MyObject instance is kept
71referenced until the action of the **addJavascriptInterface** call is cancelled
72by a call to **removeJavascriptInterface**:
73
74```Java
75// in Java
76webView.removeJavascriptInterface("myObject");
77```
78
79A more interesting situation is with transient objects returned from methods of
80an injected Java object. Consider the following example:
81
82```Java
83// in Java
84class MyObject {
85 class Handler {
86 }
87
88 @JavascriptInterface
89 public Object getHandler() { return new Handler(); }
90}
91```
92
93Again, the object returned from **`getHandler`** method is not explicitly
94referenced on the Java side, albeit it should not be disposed until it is in use
95on the JavaScript side. The "in use" period is determined by the lifetime of the
96JavaScript interface object that has been implicitly created as a result of a
97call to **getHandler** from JavaScript. That means, the instance of Handler on
98the Java side should be kept alive during the period while the corresponding
99JavaScript interface object is still referenced:
100
101```JavaScript
102// in JavaScript
103{
104 ...
105 let handler = myObject.getHandler();
106 ...
107}
108```
109
110The following figure illustrates relationships between Java and JavaScript
111objects created in the previous examples:
112
113![relationship between Java and JavaScript
114objects](images/java_bridge/relationship_java_js_objects.png)
115
116Note that Java and JavaScript VMs are absolutely independent and unaware of each
117other's existence. They can work in different processes and, in theory, even on
118different physical machines. Thus, the depicted references from JavaScript
119objects to Java objects are virtual—they don't exist directly. Instead, it is
120the Java bridge who holds the Java objects for as long as it is needed. We would
121like to depict that, but first we need to consider the whole picture.
122
123So far, we were thinking about Java bridge in abstract terms. But in fact, it is
124used in the context of a WebView-based application. The Java side of the bridge
125is tightly coupled to an instance of WebView class, while bridge's JavaScript
126side is bound to a HTML rendering engine. This is further complicated by the
127facts that in the Chromium architecture renderers are isolated from their
128controlling entities, and that Chromium is mainly implemented in C++, but needs
129to interact with Android framework which is implemented in Java.
130
131Thus, if we want to depict the architecture of Java bridge, we also need to
132include parts of the Chromium framework that are glued to Java bridge:
133
134![Java bridge architecture](images/java_bridge/java_bridge_architecture.png)
135
136The figure is now much scarier. Let's figure out what is what here:
137- In Java VM (browser side):
138 **WebView** is android.webkit.WebView class. It is exposed to the embedder and
139 interacts with Chromium rendering machinery. WebView owns a retaining set
140 (**`Set<Object>`**) that holds all injected objects to prevent their
141 collection. Note that WebView class manages a C++ object called
142 **WebContents** (in fact, the relationship is more complex, but these details
143 are not relevant for us). As Java Bridge implementation is in C++, the
144 retaining set is actually managed by the C++ side, but objects from the
145 native side do not hold any strong references to it, as that would create a
146 cyclic reference and will prevent the WebView instance from being collected.
147- On the C++ browser side:
148 Here we have the aforementioned **WebContents** object which delegates Java
149 Bridge-related requests to **JavaBridgeHost**. WebContents talks to the
Nate Fischerff389b62021-11-25 11:28:38150 objects on the renderer side via Chromium's IPC mechanism.
Shimi Zhangda40d0f2020-09-03 20:17:27151- On the C++ renderer side:
152 **RenderFrame** corresponds to a single HTML frame and it "owns" a JavaScript
153 global context object (aka **window**). For each JavaScript interface object,
Nate Fischerff389b62021-11-25 11:28:38154 a corresponding **JavaBridgeObject** instance is maintained. In Chromium
Shimi Zhangda40d0f2020-09-03 20:17:27