Execute JavaScript function

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

Execute JavaScript function

Postby edominic » Tue Apr 28, 2015 8:24 am

Hey ;)
I hope you can help me.

At first some code:
Code: Select all
var app;
if (!app)
  app = {};
(function() {
 /* removed some unnecessary code */
  app.setMessageCallback = function(name, callback)
  {
     var gwtRequest = name + "||" + callback;

     window.cefQuery({
        request: 'setMessageCallback:' + gwtRequest,
        onSuccess: function(response)
        {
           //callback(response);
        },
        onFailure: function(error_code, error_message)
        {
           console.log("setMessageCallback: " + error_message);
        }
     });


How the function will be called:

Code: Select all
function xyzAdapter(name, params, callback)
{
   app.sendMessage(name, [params]);
   
   //console.log("xyzAdapter: " + name + " ; " + params + " ; " + callback);

   var func = function(name, returnValue)
     {
        if(callback && typeof(callback) === "function")
        {
           console.log( name +" = "+ returnValue );
        
           callback( returnValue );
        }
        else
        {
            console.log("callback !== function : "+callback);
        }   
     };

   app.setMessageCallback(name, func);
}


My problem is, I can't execute this function from Java (cefbrowser.executeJavascript(...))

The result from
Code: Select all
app.sendMessage()
will be used in setMessageCallback. I know that cefQuery has a callback, too. But because of compatibility reasons, I HAVE TO to it this way. Maybe in future, we can replace this with cefQuery callback.

With sendMessage I send the name of a function and parameters for this function. This function will be executed and the result will be saved (for setMessageCallback).
With setMessageCallback I send the name of the same function (for identification) and a function:
Code: Select all
   var func = function(name, returnValue)
     {
        if(callback && typeof(callback) === "function")
        {
           console.log( name +" = "+ returnValue );
           callback( returnValue );
        }
        else
        {
            console.log("callback !== function : "+callback);
        }   
     };


callback is a parameter of xyzAdapter and returnValue is result of sendMessage.

I hope, you understand what I mean. If not, feel free to ask :)

Thanks ;)

Edit:
With CEF3 (not java) we solved it that way:
Code: Select all
            } else if (name == "setMessageCallback") {
                // Set a message callback.
                if (arguments.size() == 2 && arguments[0]->IsString() &&
                    arguments[1]->IsFunction()) {
                    std::string name = arguments[0]->GetStringValue();
                    CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
                    int browser_id = context->GetBrowser()->GetIdentifier();
                    client_app_->SetMessageCallback(name, browser_id, context,
                                                    arguments[1]);
                    handled = true;
                }


and

Code: Select all
   // Execute the registered JavaScript callback if any.
    if (!callback_map_.empty()) {
        CefString message_name = message->GetName();
        CallbackMap::const_iterator it = callback_map_.find(
                                                            std::make_pair(message_name.ToString(),
                                                                           browser->GetIdentifier()));
        if (it != callback_map_.end()) {
            // Keep a local reference to the objects. The callback may remove itself
            // from the callback map.
            CefRefPtr<CefV8Context> context = it->second.first;
            CefRefPtr<CefV8Value> callback = it->second.second;
           
            // Enter the context.
            context->Enter();
           
            CefV8ValueList arguments;
           
            // First argument is the message name.
            arguments.push_back(CefV8Value::CreateString(message_name));
           
            // Second argument is the list of message arguments.
            CefRefPtr<CefListValue> list = message->GetArgumentList();
            CefRefPtr<CefV8Value> args = CefV8Value::CreateArray(list->GetSize());
            SetList(list, args);
            arguments.push_back(args);
           
            // Execute the callback.
            CefRefPtr<CefV8Value> retval = callback->ExecuteFunction(NULL, arguments);
            if (retval.get()) {
                if (retval->IsBool())
                    handled = retval->GetBoolValue();
            }
           
            // Exit the context.
            context->Exit();
        }
    }
edominic
Mentor
 
Posts: 82
Joined: Fri Mar 13, 2015 6:46 am

Re: Execute JavaScript function

Postby kaiklimke » Wed Apr 29, 2015 12:15 am

Hi edominic,
I don't understand everything from your code, but I try:
1) First call in JS will be app.sendMessage(...) which will do something
2) You're creating a CB-Function "func" which should be called after some Java code was executed successful by "onQuery"

Have you tried to set breakpoints within the dev-tools and to trace your JS?
(simplest way: define a remote debug port and connect to it via chrome)

My suggestion of the problem:
(1) You're creating a function on the stack: var func = function(name, returnValue)
and you pass it over to the stack of the next function: app.setMessageCallback(name, fund)
(2) The second function (setMessageCallback) calls your JS Binding (the Java code) with window.cefRequest(..).
(3) Now the important JCEF-internal thing:
(*) After calling window.cefRequest, this request will be forwarded from the render process to the browser process (where the Java lives in)
(*) On the same time (in parallel) the JS code returns from window.cefQuery (because it is handled asynchronous !) and exits your function app.setMessageCallback.
(*) The result: The callback function is removed from the stack and therefore not accessible any more.
(*) The same is true for the definition "var func = function" within xyzAdapter(...)

Try to store your callback function in a global space, that means for example within your "app" object. Something like that:

Code: Select all
app = {};
(function() {
 /* removed some unnecessary code */
  app.setMessageCallback = function(name, callback)
  {
     // store function to survive this function call
     app.myCallback = callback;

     var gwtRequest = name + "||" + callback;

     window.cefQuery({
        request: 'setMessageCallback:' + gwtRequest,
        onSuccess: function(response)
        {
           //app.myCallback(response);
        },
        onFailure: function(error_code, error_message)
        {
           console.log("setMessageCallback: " + error_message);
        }
     });


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

Re: Execute JavaScript function

Postby edominic » Wed Apr 29, 2015 12:48 am

kaiklimke wrote:Hi edominic,
I don't understand everything from your code, but I try:
1) First call in JS will be app.sendMessage(...) which will do something

That's right. app.sendMessage(1. param, 2. param)
First parameter is for identification (and is a "action"-name in our java application) and second parameter are arguments for this action function)
kaiklimke wrote:2) You're creating a CB-Function "func" which should be called after some Java code was executed successful by "onQuery"

Then I will call app.setMessageCallback(1. param, 2. param)
First parameter is for identification again. Second parameter is our callback-function which should be called, after the function (1. param in sendMessage) in sendMessage is finished.

Will look in your suggestion now, and will give feedback ;) Thank you

Edit: Maybe a little example:

A JS-function which should be called through callback:

Code: Select all
function getLastSelectedLoginData(response)
{
   response = JSON.parse(response);
   
   for(var i in response)
   {...


Then, adapter function is called:

Code: Select all
xyzAdapter("getLastSelectedLoginData", '{"getLastLoginData": true}', getLastSelectedLoginData);

(1. param = action name/function in java; 2. param = parameter for this func; 3. param = JS-function callback (see above))

Here again xyzAdapter function:
Code: Select all
function xyzAdapter(name, params, callback)
{
   app.sendMessage(name, [params]);
   var func = function(name, returnValue)
     {
        if(callback && typeof(callback) === "function")
        {
           console.log( name +" = "+ returnValue );
           callback( returnValue );
        }
        else
        {
            console.log("callback !== function : "+callback);
        }   
     };
   app.setMessageCallback(name, func);
}


There you can see, callback parameter is not used directly in setMessageCallback. It is used in a new defined function (
Code: Select all
callback( returnValue );
) which will be given to setMessageCallback.

And here setMessageCallback again:

Code: Select all
  app.setMessageCallback = function(name, callback)
  {
     var gwtRequest = name + "||" + callback;

     window.cefQuery({
        request: 'setMessageCallback:' + gwtRequest,
        onSuccess: function(response)
        {
           //Not used at the moment
        },
        onFailure: function(error_code, error_message)
        {
           console.log("setMessageCallback: " + error_message);
        }
     });
  };


Maybe now you can understand it better ;)

Edit2:
So the call in java looks:
Code: Select all
    private void runJavascript(String callback, String name, String response)
    {
        if(cefBrowser != null)
        {
            String script = callback + "(" + name + "," + response + ");";
            System.out.println(script);
            cefBrowser.executeJavaScript(script, cefBrowser.getURL(), 42);
        }
    }

In callback there is
Code: Select all
function(name, returnValue) {...}
(from xyzAdapter) stored and I want to call this function with name + response as parameters, but I don't know how to do this :/
So, is there a solution to mark
Code: Select all
String callback
as a V8/JS function, so I can call from Java
Code: Select all
V8Function func = new V8Function(callback);
func.ExecuteFunction(parameters)
or some such thing

Edit3:
xyzAdapter is not the only function which uses setMessageCallback, it could be called from everywhere

Edit4:
I could resolve the problem with java, I rewrote runJavaScript function:
Code: Select all
    private void runJavascript(String callback, String name, String response)
    {
        if(cefBrowser != null)
        {
            //String script = callback + "(" + name + "," + response + ");";
            String script = "app.myCallback(\"" + name + "\", \"" + response + "\");";
            System.out.println(script);
            cefBrowser.executeJavaScript("app.myCallback = " + callback, cefBrowser.getURL(), 42);
            cefBrowser.executeJavaScript(script, cefBrowser.getURL(), 42);
            cefBrowser.executeJavaScript("app.myCallback = null;", cefBrowser.getURL(), 42);
        }
    }

It works fine, but there is another problem. In xyzAdapter there is one parameter called callback (where callback function is stored)
this callback parameter is used in
Code: Select all
var func = function(name, returnValue) {...}

If I execute Javascript from "runJavascript" function, the callback parameter is unknown and get following error:
Code: Select all
[0429/105345:INFO:CONSOLE(44)] "Uncaught ReferenceError: callback is not defined", source: file:///(...) (44)

What can I do to solve this problem. If I make callback global, it could be overwritten before callback is executed, because you can call xyzAdapter faster than sendMessage could process functions.

Edit5:
After much try and error I got a solution, but don't know if its "good" or if you have a better solution.
Define global array:
Code: Select all
var Callbacks = [];
at javascript-header
and then in xyzAdapter:
Code: Select all
function xyzAdapter(name, params, callback)
{
   app.sendMessage(name, [params]);
   Callbacks[name] = callback;

   var func = function(name, returnValue)
     {
        if(Callbacks[name] && typeof(Callbacks[name]) === "function")
        {
           console.log( "Name: " + name +" = "+ returnValue );
           Callbacks[name]( returnValue );
        }
        else
        {
            console.log("callback !== function : "+Callbacks[name]);
        }   
     };
   app.setMessageCallback(name, func);
}
edominic
Mentor
 
Posts: 82
Joined: Fri Mar 13, 2015 6:46 am

Re: Execute JavaScript function

Postby edominic » Mon May 04, 2015 1:49 pm

???
edominic
Mentor
 
Posts: 82
Joined: Fri Mar 13, 2015 6:46 am

Re: Execute JavaScript function

Postby kaiklimke » Tue May 05, 2015 12:05 am

Hi,
first of all: If you need a response please use the "reply" function and not the "edit" function of this thread. Why? Because if you add something with "reply" then interested people get a mail notification and can reply. In case you use "edit" no mail-notification is sent out.

@Edit1: Thanks for clarification.
@Edit2: Calling/using V8 in Java isn't possible because the whole Java code is executed within the browser process but V8 is within the renderer process which isn't easily accessible via Java. But maybe in an future implementation of JCEF it will be possible... Helpers are welcome.
@Edit4: Just one optimization note: you're using 3 times "executeJavaScript" one after another. You can combine those three lines to one. Example:
Code: Select all
            String script = "app.myCallback(\"" + name + "\", \"" + response + "\");";
            System.out.println(script);
            String asyncScript = "app.myCallback = " + callback + "; " + script + "; app.myCallback = null;";
            cefBrowser.executeJavaScript(asyncScript, cefBrowser.getURL(), 42);


According
It works fine, but there is another problem. In xyzAdapter there is one parameter called callback (where callback function is stored)
this callback parameter is used in

That was what I meant within my first reply. You cannot use the "callback" parameter within the callback function because it is out of scope after the asynchronous callback handling.

@Edit5: Using a global array to store several function references at once will be an option. But I would suggest not to use "name" as array index because the name might not be unique, or? You can use a randomized token which you add to the "name" and pass this identifier into Java and back.

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

Re: Execute JavaScript function

Postby edominic » Tue May 05, 2015 12:52 am

Thanks for your reply ;)
In future, I will use "Reply" instead of "Edit" thanks :)

Will look into it later (There is missing a thumbs-up-emoticon :D)
edominic
Mentor
 
Posts: 82
Joined: Fri Mar 13, 2015 6:46 am


Return to JCEF Forum

Who is online

Users browsing this forum: No registered users and 96 guests