1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
android_webview / docs / java-bridge.md [blame]
# 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](/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](https://developer.android.com/reference/android/webkit/WebView.html)
class:
- [public void **addJavascriptInterface**(Object **object**, String
**name**)](https://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface(java.lang.Object,%20java.lang.String))
-- injects a Java **object** into a WebView under the given **name**;
- [public void **removeJavascriptInterface**(String
**name**)](https://developer.android.com/reference/android/webkit/WebView.html#removeJavascriptInterface(java.lang.String))
-- removes an object previously injected under the **name**.
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`](https://developer.android.com/reference/android/webkit/JavascriptInterface.html)
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](https://www.oracle.com/java/technologies/javase/liveconnect-docs.html). 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:
```Java
// 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**:
```Java
// 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:
```Java
// 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:
```JavaScript
// 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](images/java_bridge/relationship_java_js_objects.png)
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](images/java_bridge/java_bridge_architecture.png)
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 <iframe>
tags). Each of these frames in fact has it own global context (and can even be
prevented from accessing other frames). According to Java Bridge rules, each
named object is injected into contexts of all frames. So if we imagine that we
have loaded an HTML document with an <iframe> into WebView, and then repeated
the calls from above in both main document and the <iframe>, we will have the
following picture:
![Java bridge architecture for
frames](images/java_bridge/java_bridge_architecture_frames.png)
Note that as **MyObject.getHandler()** returns a new **Handler** instance every
time, we have two instances of Handler (one per frame), but still have only one
instance of **MyObject**.
Would **getHandler** return the same instance of Handler every time, the latter
will also have multiple JavaScript interface referencing it. Thus, transient
Java object must be kept alive by Java Bridge until there is at least one
corresponding JavaScript interface object (note that Java side could keep only a
weak reference to the single Handler instance it returns, so Java Bridge must
keep its own strong reference anyway).
To summarize the lifecycle topic, here is a state diagram of a Java object
lifecycle from the Java Bridge's perspective:
![Object lifecycle](images/java_bridge/lifecycle.png)
In the states with bold borders, the Java object is retained by Java Bridge to
prevent its collection. It is possible that a garbage-collected object still has
JavaScript wrappers (that is, remains "injected"). In that case, attempts to
call methods of this object will fail.
The only difference between "Not retained, injected" and "Ordinary Java object"
states is that in the former, the Java object is still "known" to the JavaScript
side, so it can still make calls to it.
Please also note that there is no way for a named injected object to become a
transient one, although the opposite is possible.
## Arguments and Return Values Conversions
Three major problems must be addressed by Java Bridge:
1. Java primitive types are different from JavaScript types:
a. JavaScript only has Number, while Java offers a range of numeric types;
a. JavaScript has 'null' and 'undefined';
a. JavaScript has "array-like" objects that can also have string keys.
1. Java methods accept fixed number of arguments and can be overloaded, while
JavaScript methods accept any number of arguments and thus can't be
overloaded.
1. Java objects can be returned from Java methods, and previously injected Java
objects can be passed back to JavaScript interface methods.
The first problem is in fact the easiest one. Type conversions are described in
Sun Live Connect 2 spec, the only issue is that Java Bridge doesn't closely
follow the spec (for compatibility with earlier versions?). Such deviations are
marked as LIVECONNECT_COMPLIANCE in Java Bridge code and tests.
When coercing JavaScript "array-like" objects into Java arrays, only indexed
properties are preserved, and named properties are shaved off. Also, passing an
arbitrary JavaScript dictionary object via an interface method is impossible—it
is simply converted into 0, "", or null (depending on the destination Java
type).
For dealing with method overloading, the spec proposes a cost-based model for
methods resolution, where the "most suitable" Java overloaded method version is
selected. Android Java Bridge implementation in fact simply selects **an
arbitrary** overloaded method with the number of arguments matching the actual
number of parameters passed to the interface method and then tries to coerce
each value passed into the destination Java type. If there is no method with
matching number of arguments, the method call fails.
The problem with passing references to objects is to preserve the correspondence
between Java objects and JavaScript interfaces. Curiously, the NPAPI-based Java
Bridge implementation was failing to do that properly when returning Java
objects from methods. With the following Java object:
```Java
// in Java
class MyObject {
@JavascriptInterface
public Object self() { return this; }
}
...
webView.addJavascriptInterface(new MyObject(), "myObject");
```
The following equality check in JavaScript would fail (in the NPAPI
implementation):
```JavaScript
// in JavaScript
myObject.self() === myObject;
```
This is because the NPAPI Java Bridge implementation creates a new JavaScript
wrapper every time an object is returned. This issue was fixed in the Gin-based
implementation.
## Threading
Threading issues need to be considered when dealing with invocations of methods
of injected objects. In accordance with the API definition, methods are invoked
on a dedicated thread maintained by WebView.
Calls to interface methods are synchronous—JavaScript VM stops and waits for
a result to be returned from the invoked method. In Chromium, this means that
the IPC message sent from a renderer to the browser must be synchronous (such
messages are in fact rarely used in Chromium).
The requirement for serving the requests on the background thread means that the
following code must work (see
[https://crbug.com/438255](https://crbug.com/438255)):
```Java
// in Java
class Foo {
@JavascriptInterface
void bar() {
// signal the object
}
}
webview.addJavascriptInterface(new Foo(), "foo");
webview.loadUrl("javascript:foo.bar()");
// wait for the object
```
To fulfill this, the browser UI thread must not be involved in the processing of
requests from the renderer.
## Security Issues
From the very beginning, Java Bridge wasn't very much secure. Until JellyBean
MR1 (API level 17), all methods of injected Java objects were exposed to
JavaScript, including methods of java.lang.Object, most notably getClass, which
provided an elegant way to run any system command from JavaScript:
```JavaScript
// in JavaScript
function execute(bridge, cmd) {
return bridge.getClass().forName('java.lang.Runtime')
.getMethod('getRuntime',null).invoke(null,null).exec(cmd);
}
```
In JB MR1, the `@JavascriptInterface` annotation was introduced to explicitly
mark methods allowed to be exposed to JavaScript. But this restriction only
applied to applications targeting API level 17 or above, so old apps remained
insecure even on new Android versions. To fix that, in KitKat MR2 we are
forbidding to call `getClass` of `java.lang.Object` for all applications.
The next issue comes from the fact that injected Java objects are shared between
frames. This allows frames, otherwise isolated (for example, due to cross-origin
policy), to interact. For example, if an injected object has methods
'storePassword' and 'getPassword', then a password stored from one frame can be
retrieved by another frame. To prevent this, instead of injecting an object
itself, a stateless factory must be injected, so each frame will be creating its
own set of Java objects.