Asynchronous JavaScript Bindings

Having problems with building or using the JCEF Java binding? Ask your questions here.

Asynchronous JavaScript Bindings

Postby bluechips23 » Tue Nov 11, 2014 11:38 am

I am new to JCEF, so I am attempting to figure out the best way to integrate my own JS API with the browser. My question is related to the custom MessageRouterHandler class (which extends CefMessageRouterHandlerAdapter).

From JavaScript Integration introduction, I read that
JS APIs that communicate between the browser and renderer processes should be designed using asynchronous callbacks


I started playing with the source code for the Detailed Test Application, and I noticed in MessageRouterHandler and MessageRouterHandlerEx classes that a binding is identified by doing
Code: Select all
request.indexOf("BindingTest:") == 0
or
Code: Select all
request.startsWith({"hasExtension")

1. So my first question is, is there a better way to identify those bindings without looking into the request each time?


Also, the onQuery method call is something like this:
Code: Select all
public boolean onQuery(CefBrowser browser,
                         long query_id,
                         String request,
                         boolean persistent,
                         CefQueryCallback callback)

2. How should the query_id be used and what sort of purpose does it have?


On the client side (on the html page), the code is as follows:
window.cefQuery({
request: 'BindingTest:' + document.getElementById("message").value,
onSuccess: function(response) {
document.getElementById('result').value = 'Response: '+response;
},
onFailure: function(error_code, error_message) {}
});

3. Is there a way to send some sort of information or bindingID, so that it always points to it's own MessageRouterHandler?

4. What other data can we send in window.cefQuery, besides request, onSuccess and onFailure?

and last one,

The client web app wants to send some data and then receive a response back from my back end server. Unfortunately the backend server is not a web service and doesn't understand AJAX. So I wrote a JS API that will use CEF to send data to my backend server and get a response. the code is somewhat like this:

On the client web app JS:
Code: Select all
var myCustomSender = new MyCustomSender();
myCustomSender.send(messageID, message, mySuccessCallBack, myErrorCallBack);

// define mySuccessCallBack
// define myErrorCallBack


On my JavaScript API:
Code: Select all
MyCustomSender.prototype.send = function(options) {
window.cefQuery({
   request: options.messageID + "," + options.message,
   onSuccess: function(response) {
   options.mySuccessCallBack(response);
 },
  onError: function(errorCode, errorMessage) {
   options.myErrorCallBack(errorCode, errorMessage);
 }
});
}


On my own custom MessageRouterHandler:
Code: Select all
public boolean onQuery(CefBrowser browser,
                            long query_id,
                            String request,
                            boolean persistent,
                            CefQueryCallback callback) {
       if (request.indexOf("BindingID") == 0) {  [b]// This part is related to question 3[/b]
         // Send data to my backend Java Server and get a response
         callback.success(response);
         return true;
       }
       // Not handled.
       return false;
     }


5. So the question is, is this the right approach to send data to a backend server using CEF?

My questions can seem a little confusing, and if it is, please let me know, and I can try more to explain them better.

Thanks!
bluechips23
Techie
 
Posts: 12
Joined: Tue Nov 11, 2014 10:58 am

Re: Asynchronous JavaScript Bindings

Postby magreenblatt » Tue Nov 11, 2014 12:17 pm

bluechips23 wrote:1. So my first question is, is there a better way to identify those bindings without looking into the request each time?

The request body is opaque from CEF's perspective. You can use whatever data format is appropriate for your application -- strings, json, xml, etc.

bluechips23 wrote:2. How should the query_id be used and what sort of purpose does it have?

See cef_message_router.h for complete usage instructions.

bluechips23 wrote:3. Is there a way to send some sort of information or bindingID, so that it always points to it's own MessageRouterHandler?

You can include this information in the request body and route as appropriate for your application.

bluechips23 wrote:4. What other data can we send in window.cefQuery, besides request, onSuccess and onFailure?

Encode whatever data you want to send in the request.

bluechips23 wrote:5. So the question is, is this the right approach to send data to a backend server using CEF?

You should perform communication with your back-end server on a separate thread and execute CefQueryCallback asynchronously once the response is available.
magreenblatt
Site Admin
 
Posts: 12384
Joined: Fri May 29, 2009 6:57 pm

Re: Asynchronous JavaScript Bindings

Postby bluechips23 » Tue Nov 11, 2014 1:45 pm

Thank you! This is perfect.
bluechips23
Techie
 
Posts: 12
Joined: Tue Nov 11, 2014 10:58 am

Re: Asynchronous JavaScript Bindings

Postby kaiklimke » Wed Nov 12, 2014 3:22 am

Hi bluechips23,

1. So my first question is, is there a better way to identify those bindings without looking into the request each time?


Beside Marshalls suggestions, you can register multiple instances of CefMessageRouter with different names for the JS functions instead of "cefQuery" and "cefQueryCancel".
This will give you the possibility to react different on different function from JS.

Example:
Code: Select all
public class YourClass {

  void someWhereInYourCode() {
    CefClient client = CefApp.getInstance().createClient();

    CefMessageRouter msgRouterA = CefMessageRouter.create(new CefMessageRouterConfig("myQueryA", "myCanelA"));
    msgRouterA.addHandler(new MessageRouterHandlerA(), true);

    CefMessageRouter msgRouterB = CefMessageRouter.create(new CefMessageRouterConfig("myQueryB", "myCanelB"));
    msgRouterB.addHandler(new MessageRouterHandlerB(), true);

    client.addMessageRouter(msgRouterA);
    client.addMessageRouter(msgRouterB);

    [...]
  }
}

public class MessageRouterHandlerA extends CefMessageRouterHandlerAdapter {
  @Override
  public boolean onQuery(CefBrowser browser, long query_id, String request, boolean persistent, CefQueryCallback callback) {

    // Do something with the JS call of window.myQueryA();

    callback.success("myAnswerToA");
    return true;
  }
}

public class MessageRouterHandlerB extends CefMessageRouterHandlerAdapter {
  @Override
  public boolean onQuery(CefBrowser browser, long query_id, String request, boolean persistent, CefQueryCallback callback) {

    // Do something with the JS call of window.myQueryB();

    callback.success("myAnswerToB");
    return true;
  }
}


In the short (not tested) example, I've registered two message router handlers. One for handling calls to JS function "window. myQueryA('....');" and one for handling calls to the JS function "window. myQueryB('....');"
The MessageRouterHandlerEx is choosing a similar kind of approach.

Regards,
Kai
kaiklimke
Techie
 
Posts: 19
Joined: Tue Oct 29, 2013 3:49 am

Re: Asynchronous JavaScript Bindings

Postby bluechips23 » Wed Nov 12, 2014 2:45 pm

Kai,

This is great. I think this one fits my goal perfectly. Thanks!
bluechips23
Techie
 
Posts: 12
Joined: Tue Nov 11, 2014 10:58 am

Re: Asynchronous JavaScript Bindings

Postby bluechips23 » Thu Nov 13, 2014 11:27 am

Forgive me, but I have couple more follow up questions regarding JavaScript in JCEF. Please let me know if you need me to create a separate thread for these questions:

1. I have read through the documentation for JavaScript Integration and I know that in C++ library for CEF, there are ways to execute JavaScript functions with call backs using ExecuteFunction() and ExecuteFunctionWithContext() methods. I was wondering if these methods are also implemented on JCEF.

On JCEF, I only found executeJavaScript() method for CefBrowser, but are there any more related methods for executing JavaScript?

2. Please confirm whether my understanding from the documentations is correct:
[list=]we can use executeJavaScript() or executeFunction() methods when we want the frame to load JavaScript on the page. We can't get data back from the browser frame back to JCEF using these methods.[/list]
[list=]If we want to send data from the browser frame back to JCEF, then we have to use window.cefQuery() in browser-side router that sends the request. [/list]

3. You mentioned in your response earlier that,
magreenblatt wrote:Encode whatever data you want to send in the request.
and
magreenblatt wrote:The request body is opaque from CEF's perspective. You can use whatever data format is appropriate for your application -- strings, json, xml, etc.


Would you please be able to show me an example how to receive my request as ArrayBuffer? I am guessing the JavaScript implementation would be something like this:
Code: Select all
window.cefQuery({
      request: //my request in ArrayBuffer
      persistent: true,
      onSuccess: function(response) {
         // success call back
      },
      onFailure: function(errorCode, errorMessage) {
         // error call back
      }
   });

My confusion is how do I get that ArrayBuffer (probably as ByteBuffer on Java side) in my MessageRouterHandler's onQuery() method?
bluechips23
Techie
 
Posts: 12
Joined: Tue Nov 11, 2014 10:58 am

Re: Asynchronous JavaScript Bindings

Postby magreenblatt » Thu Nov 13, 2014 5:30 pm

bluechips23 wrote:1. I have read through the documentation for JavaScript Integration and I know that in C++ library for CEF, there are ways to execute JavaScript functions with call backs using ExecuteFunction() and ExecuteFunctionWithContext() methods. I was wondering if these methods are also implemented on JCEF.

These methods execute in the renderer process. JCEF does not currently support Java code executing in the renderer process.

bluechips23 wrote:My confusion is how do I get that ArrayBuffer (probably as ByteBuffer on Java side) in my MessageRouterHandler's onQuery() method?

You cannot use an ArrayBuffer object as-is. You need to serialize it into a string using something like base64 encoding.
magreenblatt
Site Admin
 
Posts: 12384
Joined: Fri May 29, 2009 6:57 pm

Re: Asynchronous JavaScript Bindings

Postby kaiklimke » Fri Nov 14, 2014 2:14 am

Hi bluechips23,
We can't get data back from the browser frame back to JCEF using these methods. [...] f we want to send data from the browser frame back to JCEF, then we have to use window.cefQuery() [...]


Yes, that's true but you can use the same pattern to send requests from Java to JS as used in JavaScript.
Here is a complete working example:

The HTML Code has two JavaScript functions:
(1) "makeCoffee" is a wrapper around the cefQuery call and sends some data to Java. On success it shows up with an alert and the computed reply.
(2) "addSugar" is a function which gets called from Java. The first parameter is a string, the second one is a closure function, to return the result to Java.
Code: Select all
<html>
<head>
<title>Make Coffee</title>

<script type="text/javascript">

function addSugar(what, closure) {
  result = what + " and sugar";
  closure(result);
}

function makeCoffee(coffee) {
  window.doIt({
    request: '1:'+coffee,
    onSuccess: function(response) {
      alert("Your coffee is ready: " + response);
    },
    onFailure: function(errCode, errMsg) {
      alert("No coffee today: " + errMsg);
    }
  });
}
</script>
</head>
<body>
<div>
<a href="javascript:makeCoffee('A big cup Coffee');">I need Coffee</a><br/>
</div>
</body>


As first step in Java, I'm registering the JS Handler with the binding name "doIt" and in case of an abort with "dontDoIt".
Code: Select all
[...]
        MyJsHandler myJsHandler = new MyJsHandler();
        CefMessageRouterConfig myCfg = new CefMessageRouterConfig("doIt","dontDoIt");
        CefMessageRouter myJSRouter = CefMessageRouter.create(myCfg);
        myJSRouter.addHandler(myJsHandler, true);
        cefClient.addMessageRouter(myJSRouter);
[...]


And here is the code of the CefMessageRouterHandler implementation.
(1) If it receives a request with the label "1:" it stores the callback object and the request string.
(1.2) After that a request is send back to JavaScript. Therefore the JS-function "addSugar" is called. As you can see, the second parameter is now a function definition which calls back to the Java binding "doIt". Now with the label "2:".
(1.3) After executeJavaScript was fired, the onQuery method returns with "true" (yes, I handled the request), but the callback isn't fired, yet.

(2) In the meanwhile the fired JavaScript is executed and returns to the handler with the label "2:"
(2.1) First the callback of the second JS call is submitted, so that the call to "addSugar" is finished.
(2.2) The received answer from the JS->J->JS->J call is concatenated to the initial request and returned to the callback of the first call.
(2.3) Return with true and the process is done

You will see an alert on your screen with a concatenated string of the following values:
(1) "A big cup Coffee" (came from JS)
(2) "with milk" (came from J)
(3) "and sugar" (came from JS)

Code: Select all
public class MyJsHandler extends CefMessageRouterHandlerAdapter  {
    private CefQueryCallback lastCallback = null;
    private String lastRequest = null;

    @Override
    public boolean onQuery(CefBrowser browser, long query_id, String request,
            boolean persistent, CefQueryCallback callback) {

        if (request.startsWith("1:")) {
            // 1) First call to onQuery. Store callback and make an async JS call
            //    to receive more values
            if (lastCallback != null)
                lastCallback.failure(42, "aborted by another request");
            lastCallback = callback;
            lastRequest = request.substring(2);

            String myJSCall = "addSugar('with milk',function(result) {" +
                    "window.doIt({" +
                    "request: '2:' + result," +
                    "onSuccess: function(response) {}," +
                    "onFailure: function(errorCode, errorMessage) {alert('errorMessage');}});});";
            browser.executeJavaScript(myJSCall, browser.getURL(), 42);
            return true;

        } else if (request.startsWith("2:")) {
            // 2) Second call to onQuery. Create a final result according the first
            //    call request and the second call request.

            if (lastCallback != null) {
                String response = lastRequest + " " + request.substring(2);
                // Submit current callback (to the closure function)
                // and the previous one to the origin call.
                callback.success("");
                lastCallback.success(response);
            } else {
                callback.failure(42, "Previous callback is null!");
            }
            // reset values
            lastCallback = null;
            lastRequest = null;
            return true;
        }
        return false;
    }
}


Please note: The whole error handling (implementing onQueryCaneled() etc.) should be done in a real world environment as well.
I hope this example makes it somewhat clearer how powerful this JS binding is.

You cannot use an ArrayBuffer object as-is. You need to serialize it into a string using something like base64 encoding.


I would prefer to send each request string from JS to Java as JSON structure. Then using a JSON-Parser within Java and transmitting the result back as JSON.

Regards,
Kai
kaiklimke
Techie
 
Posts: 19
Joined: Tue Oct 29, 2013 3:49 am

Re: Asynchronous JavaScript Bindings

Postby bluechips23 » Fri Nov 14, 2014 10:10 am

Kai and Magreenblatt - you guys are awesome.

I think I have a very good idea about how I should be building my application with JCEF.

Thank you!
bluechips23
Techie
 
Posts: 12
Joined: Tue Nov 11, 2014 10:58 am


Return to JCEF Forum

Who is online

Users browsing this forum: No registered users and 20 guests