Providing a new mechanism for Native<-> JS communication in Android Webview

2,647 views
Skip to first unread message

Selim Gurun

unread,
Feb 3, 2014, 9:19:58 PM2/3/14
to chromi...@chromium.org
Hello,

I am in the Android Webview team. I would like to receive some early feedback on potential improvements to Native App <-> JS communication in Android Webview (http://developer.android.com/reference/android/webkit/WebView.html). In Chromium architecture, Android Webview  is a layer above src/content and does not depend on src/chrome. 

At present, applications using WebView can use addJavaScriptInterface() method to inject a Java object to JS context:
However this is not a very desirable method as it opens the application to malicious code when content is not trusted.

We are now investigating a mechanism that is closer to the existing standards. One potential API is designing sth similar to window.postMessage(message, targetOrigin, [transfer]). Without looking too much at the machinery underneath, I think the API can be sth like:

    myObject.postMessage(message, targetOrigin) 
where myObject is the name of the object injected to JS, message is the message and targetOrigin is the name of the application. Some questions to address are whether targetOrigin is really needed and if it is acceptable to have myObject being overloaded (a Java object vs. window).

Another option is using an API similar to chrome.runtime.sendNativeMessage(). This API is not really similar to how Webview objects interact, (i.e. it looks like the lifetime of native application is controlled by JS - Chrome starts application when a native message/communication is initiated from JS and destroys when not needed), but maybe we can create a Webview specific version that have different semantics.

The advantage of both APIs is they allow target and source origins to be enforced.

The Java side is more TBD but if we go with the first API, at the Java side we can create a public interface JsEventListener which is similar to EventListener in JS and then we can create a JsEvent class that will contain the data and origin.

interface JSEventListener {
   void handleEvent(JsEvent event);
}
class JsEvent {
       Object data;
       String Url;
}

While I am going to further continue investigating it to come up with a more concrete proposal, I was wondering if there any suggestions, opinions and guidance both for the API and the machinery involved.
Thanks,

-Selim

Ryan Sleevi

unread,
Feb 3, 2014, 9:33:47 PM2/3/14
to sgu...@chromium.org, Chromium-dev
I'm not entirely familiar with addJavascriptInterface, but are the methods exposed on the Java.lang.Object object exposed to JS via addJavascriptInterface allowed to return values? If so, postMessage isn't sufficient, as it only allows messages to go in one direction (from JS -> Java), and not to migrate back.

How do you plan to allow Java to send messages back to the WebView? Injecting script directly seems bad, especially in the existance of CSP. I would expect that the WebView object would expose a Java-style interface for a MessageChannel, and on the JS side, there's some way that it can use onmessage to handle responses - that is, similar to http://www.whatwg.org/specs/web-apps/current-work/multipage/web-messaging.html#channel-messaging



Darin Fisher

unread,
Feb 3, 2014, 11:49:46 PM2/3/14
to Selim Gurun, Chromium-dev, Fady Samuel
Going with an asynchronous message passing API sounds good to me. addJavaScriptInterface() is a very unfortunate API.

You may want to take a look at how this was done for the <webview> tag as part of the Chrome Packaged Apps platform:

It looks like the embedded page (the guest page) may only post messages to the embedder upon receipt of a message. I'm not sure why that restriction exists. +fsamuel

Once this new API is in place, is there a deprecation story for addJavaScriptInterface()? It sure would be nice to ween folks off of it somehow.

-Darin


On Mon, Feb 3, 2014 at 6:19 PM, Selim Gurun <sgu...@chromium.org> wrote:

--
--
Chromium Developers mailing list: chromi...@chromium.org
View archives, change email options, or unsubscribe:
http://groups.google.com/a/chromium.org/group/chromium-dev

Message has been deleted
Message has been deleted

Selim Gurun

unread,
Feb 4, 2014, 4:32:52 PM2/4/14
to chromi...@chromium.org
Update from Cordova people, since their post failed.
===================================================================

From Cordova's point of view, there are three relevant goals:
1. Restricting access by origin
2. Communicating from Java->JS without having to evaluate JS (we found in Cordova that small snippets of JS are super slow in V8 compared to triggering events)
3. Enabling more data types to be passed

Restricting addJsInterface by domain certainly makes sense to me. Since call made through it are synchronous and do not require synchronizing with the UI thread, I think it is still a nice API to keep around.

I'm more excited by the prospects of postMessage() though (for both Java->JS as well as JS->Java). The thing I'd be particularly interested in is expanding the types of objects that can be passed. Right now addJsInterface is restricted to ints and Strings. We send ArrayBuffers and Blobs by base64 encoding :(. If there were a way to use postMessage's transferrables concept to pass these efficiently between JS / Java, that would be great!

Selim Gurun

unread,
Feb 4, 2014, 5:46:35 PM2/4/14
to chromi...@chromium.org
The methods are allowed to return primitive types and Java objects. If a Java object is returned, then one is allowed to only call methods that are annotated with @JavascriptInterface. As Andrew from Cordova pointed out, the return values are generally useful as numbers and Strings. I can see that postMessage() may create some issues there, so I will keep a note of this.

For Java->Js communication, Webview has two APIs. WebView.loadUrl() can be called with a "javascript:" url and WebView.evaluateJavascript() can be called to evaluate a JS in the page context. These are probably considered ok in Android context, since the app is already trusted by the user downloading it. 

MessageChannel's can be interesting for duplex communication. I will take a look at them.

We are planning to deprecate addJavascriptInterface API once we have a good replacement.

Fady Samuel

unread,
Feb 4, 2014, 5:50:49 PM2/4/14
to Darin Fisher, Selim Gurun, Chromium-dev
This may not apply for the android Webview, but for <webview>, by default the guest (embedded) content is oblivious to the fact that it's embedded in something. It thinks it's a top-level frame in Chrome. Thus, the guest doesn't have a mechanism to talk back to the embedder. Once the App makes a decision to interact with the guest via postMessage or executeScript, then the guest becomes aware that it is embedded content.

Fady

Ryan Sleevi

unread,
Feb 4, 2014, 5:57:11 PM2/4/14
to Selim Gurun, Chromium-dev
It would be good to try and understand the issue you're trying to solve.

Example:
Is it related to the synchronous nature involved with addJavascriptInterface causing negative performance impact?
Is it related to security (eg: what motivated the @JavascriptInterface annotation)?
Is it something else?

For example, if it's "just" the synchronous interface, do Promises ( http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts ) make a better API? That is, would it make more sense to just have every object/method returned via addJavascriptInterface return it's result as a Promise and let the resolver handle it?

Java side:
class IHaventWrittenJavaInYears {
  @JavascriptInterface
  public String someMethod() { return "Synchronously" }
};
webView.addJavascriptInterface(new IHaventWrittenJavaInYears(), "myObject");

JS side:
var promise = myObject.someMethod();
console.log(promise.prototype);  // Logs "Promise"... I think
promise.then(console.log);  // Logs "Synchronously" - AT SOME POINT IN THE FUTURE

You could also chain

class ObjectA {
  @JavascriptInterface
  public ObjectB getObjectB() { return new ObjectB(); }
};
class ObjectB {
  @JavascriptInterface
  public String getString() { return "oh hai"; }
};
webView.addJavascriptInterface(new ObjectA(), "objectA");

var promise = objectA.someMethod().then(function(objectB) { return objectB.getString(); }).then(console.log);  // Logs "oh hai" - AT SOME POINT IN THE FUTURE

I'm just trying to make sure we're solving the right problem you want to solve.

Selim Gurun

unread,
Feb 4, 2014, 6:20:37 PM2/4/14
to rsl...@chromium.org, Chromium-dev
It is primarily security motivated. We essentially want to provide a mechanism so that applications can restrict JS->Java calls to certain origins, if they want to. One idea we entertained was to add an option such that

@JavascriptInterface [Origin]
public void foo() 

So that only scripts that originate from "Origin" can call foo. However, it seems that such origin information is not available in V8 API.

There is also a desire to implement a more "standards-like" approach similar to postMessage, due to asynchronous interface, poor documentation and idiotic behavior of addjavascriptinterface, etc but this is secondary.

TZ 天猪

unread,
Mar 28, 2014, 6:15:25 AM3/28/14
to chromi...@chromium.org, Selim Gurun, rsl...@chromium.org
I found a strange things:

compare addJavascriptInterface  and onJSPrompt, I found:

1. at nexus5 (4.4.2),  addJavascriptInterface  is slow than onJSPrompt. (11190ms vs 11022ms)
2. at other 4.1 / 4.3 ,  addJavascriptInterface  is fast than onJSPrompt.   (658ms vs 4747ms)

在 2014年2月5日星期三UTC+8上午6时57分11秒,Ryan Sleevi写道:
Reply all
Reply to author
Forward
0 new messages