addJavascriptInterface - JS <-> Java bridge

1,847 views
Skip to first unread message

Davis Ford

unread,
Sep 3, 2013, 11:56:40 AM9/3/13
to chromi...@chromium.org
Hi, in ContentViewCore, I see there is an addJavascriptInterface https://code.google.com/p/chromium/codesearch#chromium/src/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java&q=ContentViewCore&sq=package:chromium&l=2633 but is there anyway to pass a JavaScript function (either anonymous or named) into Java via the injected JavaScript interface?

Example:

// javascript side

<script>
   document.test.myfunction(function (result) {
      document.getElementById('container').innerHTML = JSON.stringify(result);
   });
</script>
<body>
  <div id="container"></div>
</body>

// java side

public class Test {

   @JavascriptInterface
   public void myfunction(Object obj) {
      // how can I call back to JavaScript from here?
   }
}

// ContentViewCore

contentViewCore.addJavascriptInterface(new Test(), "test");

I see there are methods to evaluate JavaScript from Java and get an async result in ContentViewCore, but is there no way to do the reverse path?  Also, in my experimentation, if I set the parameter type in a @JavascriptInterface as Object, it seems to pass null, but if I set it as String, it passes String objects, but JavaScript functions don't seem to pass through.

@JavascriptInterface
public void myfunction(Object obj) { /* obj is null if parameter is JS function */ }

@JavascriptInterface
public void myfunction(String obj) { /* obj is null if parameter is JS function, but it is a string if I pass a JS string */ }

Is it not possible to pass a JavaScript function reference to Java?

The only information I could rustle up about this in the Android space was this Issue 9226 https://code.google.com/p/android/issues/detail?id=9226&can=1&q=javascript%20anonymous&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars -- but that relates to Android WebView.

David Trainor

unread,
Sep 3, 2013, 12:40:05 PM9/3/13
to davi...@gmail.com, chromi...@chromium.org
Hey Davis,

Hopefully I'm understanding what you're trying to do :).  Can you call ContentViewCore#evaluateJavaScript() inside your Test#myFunction()?  If you have a more straight forward use case, you could serialize and return a JSON string and interpret it on the JavaScript side of things.  If this isn't what you need let me know.  You can find some interesting examples of using these APIs in AccessibilityInjector.java/JellyBeanAccessibilityInjector.java

Regarding passing an Object from JavaScript -> Java, is this an actual java.lang.Object or a JavaScript object?

Hope that helps,
-Dave




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

Davis Ford

unread,
Sep 3, 2013, 1:25:19 PM9/3/13
to chromi...@chromium.org, davi...@gmail.com
Hi Dave, Thanks for the help.

I can call ContentViewCore#evaluateJavaScript() inside Test#myFunction -- what I'd like to know is if there is any way to pass a JavaScript function through the bridge -- (or some representative reference to that function)?

For example, consider a common idiom in JavaScript APIs where you pass a callback function as a parameter:

/**
 * @param value {String} a value
 * @param callback {Function} a JavaScript function
 */
function echo(value, callback) {
  // echo value back
  callback(value);
}

If I wanted to create this same interface on the Java side so that it is implemented in Java, but can be called from JavaScript -- I can't seem to resolve how to pass a JS function (second param).

public class EchoChamber {
   @JavascriptInterface
   public void echo(String value, Object callback) {
      // this does not work, callback is always null
   }
}

contentViewCore.addJavascriptInterface(new EchoChamber(), "echo");

I could have a named function in JavaScript and then call ContentViewCore#evaluateJavaScript

/**
 * Named JS function for echo callback
 * @param {String} the echoed value
 */
function echoCallback(value) {
  console.log("echo callback: " + value);
}

public class EchoChamber {
  private ContentViewCore core;
  public EchoChamber(ContentViewCore core) { this.core = core; }

  @JavascriptInterface
  public void echo(String value) {
    core.evaluateJavaScript("echoCallback('" + value + "');");
  }
}

That seems to work, but it forces a certain API idiom that won't allow passing of functions -- callback functions always need to be pre-defined in the JavaScript.  The Android Issue I linked to indicated that JavaScript functions used to get serialized into a java.lang.String representation, and then one could turn around and execute that via ContentViewCore#evaluateJavaScript(), but this does not seem to be the case with Chromium.  Anytime I pass a JavaScript function as an argument it becomes null in Java-land.

Even if it did work, it still poses a problem whereby async code could be interleaved --  there doesn't seem to be any representation of an internal stack.  For example:

// javascript named callback function
function myJsCallback(val) {
  console.log('callback returned: ' + val);
}

// call a Java function that is asynchronous and will callback to myJsCallback
window.callAsyncJavaFunction('hello 1');

// call a Java function that is asynchronous and will callback to myJsCallback
window.callAsyncJavaFunction('hello 2');

If you execute this code, there's no relation between the initial call into Java, and the return callback.  They could return as "hello 1" -> "hello 2" or vice-versa

Is there any way around this -- or am I just thinking about it all wrong?

Thanks!

Davis

Newton Allen

unread,
Sep 3, 2013, 1:49:43 PM9/3/13
to Davis Ford, Chromium-dev
Might webui be useful for your purposes? It's used for creating chrome:// pages and allows for communication between JavaScript and C++. From C++ you can get to Java via JNI.

Davis Ford

unread,
Sep 3, 2013, 4:47:32 PM9/3/13
to chromi...@chromium.org, Davis Ford

Hmm...that looks interesting, thanks -- wasn't aware of it.  Perhaps it could be useful.

Otherwise, I suppose I'll have to track each request/reply manually...this seems to be how the JellyBeanAccessibilityInjector does it.  The problem with the request/reply semantics is that you lose the context of a closure.  You can call a named JavaScript function from Java, but you'll lose the closure, which makes it a non-starter.  So, I suppose the workaround is to keep a reference to each closure in JS by id, and when the async response comes back, dig up the closure by id, execute and delete it.

I suppose the V8 fundamentals are significantly buried / encapsulated from up in this layer (ContentViewCore).  Would be nice to know if there's an easier way to accomplish this using some kind of JavaScript function reference instead of doing all this accounting to keep track of execution across boundaries.

Regards,
Davis

Reply all
Reply to author
Forward
0 new messages