2

Can someone tell me what I'm doing wrong while performing this JavaScript cefpython callback on line 118?

# Tutorial example. Doesn't depend on any third party GUI framework.
# Tested with CEF Python v56.2+

from cefpython3 import cefpython as cef
import base64
import platform
import sys
import threading

# HTML code. Browser will navigate to a Data uri created
# from this html code.
HTML_code = """
<!DOCTYPE html>
<html>
<head>
   <style type="text/css">
   body,html { font-family: Arial; font-size: 11pt; }
   div.msg { margin: 0.2em; line-height: 1.4em; }
   b { background: #ccc; font-weight: bold; font-size: 10pt;
       padding: 0.1em 0.2em; }
   b.Python { background: #eee; }
   i { font-family: Courier new; font-size: 10pt; border: #eee 1px solid;
       padding: 0.1em 0.2em; }
   </style>

   <script>
   function js_print(lang, event, msg) {
       msg = "<b class="+lang+">"+lang+": "+event+":</b> " + msg;
       console = document.getElementById("console")
       console.innerHTML += "<div class=msg>"+msg+"</div>";
   }

   function js_callback_1(ret) {
       js_print("Javascript", "html_to_data_uri", ret);
   }

   function js_callback_2(msg, py_callback) {
       js_print("Javascript", "js_callback", msg);
       py_callback("String sent from Javascript XY5C");
   }

   window.onload = function(){

       js_print("Javascript", "python_property", python_property);
       html_to_data_uri("test", js_callback_1);
       external.test_multiple_callbacks(js_callback_2);
   };
   </script>
</head>


<body>
<div id="console"></div>
 <canvas id="myCanvas" width="800" height="800"></canvas>


   <script type="text/javascript">
   function js_callback_1(ret) {
       js_print("Javascript", "html_to_data_uri", ret);
   }
   function js_callback_2(msg, py_callback) {
       js_print("Javascript", "js_callback", msg);
       py_callback("String sent from Javascript 01XC");
   }

   window.onload = function(){
       js_print("Javascript", "window.onload", "Called");
       js_print("Javascript", "python_property", python_property);
       js_print("Javascript", "navigator.userAgent", navigator.userAgent);
       js_print("Javascript", "cefpython_version", cefpython_version.version);
       html_to_data_uri("test", js_callback_1);
       external.test_multiple_callbacks(js_callback_2);
   };
       (function() {
 var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
   window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
 window.requestAnimationFrame = requestAnimationFrame;
})();

//event listener
window.addEventListener("keydown", onKeyDown, false);
window.addEventListener("keyup", onKeyUp, false);

function onKeyDown(event) {
 var keyCode = event.keyCode;
 switch (keyCode) {
   case 68: //d
     keyD = true;
     break;
   case 83: //s
     keyS = true;
     break;
   case 65: //a
     keyA = true;
     break;
   case 87: //w
     keyW = true;

     break;
 }
}

function onKeyUp(event) {
 var keyCode = event.keyCode;

 switch (keyCode) {
   case 68: //d
     keyD = false;
     break;
   case 83: //s
     keyS = false;
     break;
   case 65: //a
     keyA = false;
     break;
   case 87: //w
     keyW = false;
     py_callback("String sent from Javascript 89JX");
     break;
 }
}

//neccessary variables
var tickX = 10;
var tickY = 10;

var keyW = false;
var keyA = false;
var keyS = false;
var keyD = false;

//main animation function
function drawStuff() {
 window.requestAnimationFrame(drawStuff);
 var canvas = document.getElementById("myCanvas");
 var c = canvas.getContext("2d");

 c.clearRect(0, 0, 800, 800);
 c.fillStyle = "blue";
 c.fillRect(tickX, tickY, 100, 100);

 if (keyD == true) {
   tickX += 1;
 }
 if (keyS == true) {
   tickY += 1;
 }
 if (keyA == true) {
   tickX--;
 }
 if (keyW == true) {
   tickY--;
 }
}
window.requestAnimationFrame(drawStuff);
   </script>

</body>

</html>
"""

#<body>
#    <h1>Tutorial example</h1>
#    <div id="console"></div>
#</body>
def main():
    check_versions()
    sys.excepthook = cef.ExceptHook  # To shutdown all CEF processes on error
    cef.Initialize()
    set_global_handler()
    browser = cef.CreateBrowserSync(url=html_to_data_uri(HTML_code),
                                    window_title="Tutorial")
    set_client_handlers(browser)
    set_javascript_bindings(browser)
    cef.MessageLoop()
    cef.Shutdown()

def check_versions():
    print("[tutorial.py] CEF Python {ver}".format(ver=cef.__version__))
    print("[tutorial.py] Python {ver} {arch}".format(
          ver=platform.python_version(), arch=platform.architecture()[0]))
    assert cef.__version__ >= "56.2", "CEF Python v56.2+ required to run this"


def html_to_data_uri(html, js_callback=None):
    # This function is called in two ways:
    # 1. From Python: in this case value is returned
    # 2. From Javascript: in this case value cannot be returned because
    #    inter-process messaging is asynchronous, so must return value
    #    by calling js_callback.
    html = html.encode("utf-8", "replace")
    b64 = base64.b64encode(html).decode("utf-8", "replace")
    ret = "data:text/html;base64,{data}".format(data=b64)
    if js_callback:
        js_print(js_callback.GetFrame().GetBrowser(),
                 "Python", "html_to_data_uri",
                 "Called from Javascript. Will call Javascript callback now.")
        js_callback.Call(ret)
    else:
        return ret


def set_global_handler():
    # A global handler is a special handler for callbacks that
    # must be set before Browser is created using
    # SetGlobalClientCallback() method.
    global_handler = GlobalHandler()
    cef.SetGlobalClientCallback("OnAfterCreated",
                                global_handler.OnAfterCreated)


def set_client_handlers(browser):
    client_handlers = [LoadHandler(), DisplayHandler()]
    for handler in client_handlers:
        browser.SetClientHandler(handler)


def set_javascript_bindings(browser):
    external = External(browser)
    bindings = cef.JavascriptBindings(
            bindToFrames=False, bindToPopups=False)
    bindings.SetProperty("python_property", "python_property defined in Python #X1HQ")
    bindings.SetProperty("cefpython_version", cef.GetVersion())
    bindings.SetFunction("html_to_data_uri", html_to_data_uri)
    bindings.SetObject("external", external)
    browser.SetJavascriptBindings(bindings)

def js_print(browser, lang, event, msg):
    # Execute Javascript function "js_print"
    browser.ExecuteFunction("js_print", lang, event, msg)

class GlobalHandler(object):
    def OnAfterCreated(self, browser, **_):
        # DOM is not yet loaded. Using js_print at this moment will
        # throw an error: "Uncaught ReferenceError: js_print is not defined".
        # We make this error on purpose. This error will be intercepted
        # in DisplayHandler.OnConsoleMessage.
        js_print(browser, "Python", "OnAfterCreated",
                 "This will probably never display as DOM is not yet loaded")
        # Delay print by 0.5 sec, because js_print is not available yet
        args = [browser, "Python", "OnAfterCreated",
                "(Delayed) Browser id="+str(browser.GetIdentifier())]
        threading.Timer(0.5, js_print, args).start()


class LoadHandler(object):
    def OnLoadingStateChange(self, browser, is_loading, **_):
        # This callback is called twice, once when loading starts
        # (is_loading=True) and second time when loading ends
        # (is_loading=False).
        if not is_loading:
            # Loading is complete. DOM is ready.
            js_print(browser, "Python", "OnLoadingStateChange",
                     "Loading is complete")


class DisplayHandler(object):
    def OnConsoleMessage(self, browser, message, **_):
        # This will intercept js errors, see comments in OnAfterCreated
        if "error" in message.lower() or "uncaught" in message.lower():
            # Prevent infinite recurrence in case something went wrong
            if "js_print is not defined" in message.lower():
                if hasattr(self, "js_print_is_not_defined"):
                    print("Python: OnConsoleMessage: "
                          "Intercepted Javascript error: "+message)
                    return
                else:
                    self.js_print_is_not_defined = True
            # Delay print by 0.5 sec, because js_print may not be
            # available yet due to DOM not ready.
            args = [browser, "Python", "OnConsoleMessage",
                    "(Delayed) Intercepted Javascript error: <i>{error}</i>"
                    .format(error=message)]
            threading.Timer(0.5, js_print, args).start()
            browser.ExecuteJavascript("alert('The value for \"python_property\" is: ' + python_property);")


class External(object):
    def __init__(self, browser):
        self.browser = browser

    def test_multiple_callbacks(self, js_callback):
        """Test both javascript and python callbacks."""
        js_print(self.browser, "Python", "test_multiple_callbacks",
                 "Called from Javascript. Will call Javascript callback now.")

        def py_callback(msg_from_js):
            js_print(self.browser, "Python", "py_callback", msg_from_js)
        js_callback.Call("String sent from Python H1T7", py_callback)

if __name__ == '__main__':
    main()

I understand that I'm likely using the wrong syntax, but I've tried about 15 variations and still can't figure it out. Every route either produces errors or fails to update.

(To be clear, I am trying to define a variable with a string when 'w' is pressed in JavaScript, which also moves around a blue square.)

C. Poe
  • 21
  • 1

2 Answers2

1

About the error

It could be that you are not setting some variables properly ie python_property and py_callback, but using them on the code.

Here on this code you'll see python_property is being set using,

bindings.SetProperty("python_property", "This property was set in Python")

Also here some other functions like py_callback are defined, which are called on your code but never defined.

def py_callback(msg_from_js):
        js_print(self.browser, "Python", "py_callback", msg_from_js)

How did I know this? Simply I copied all of your code in a plain html file and then opened the console. Then when I got some errors, I knew where to look for. That's not a good way to debug, but keep that in your 15+ variations too :D .

enter image description here

After a deep look into your code, it shows you are trying to access a variable called py_callback which is a local variable to the js_callback_2 function. Thus it's producing the errors.

Passing data to python

To pass data, you can use a function on the External class, which can be called and be used to pass data.

somevar = 'whatever'
class External(object):
    def __init__(self, browser):
        self.browser = browser

    def set_variable_value(self, key, value):
        somevar = value;
        print(somevar)
        # globals()[key] = value;
        # print(globals()[key])

Now, if I've defined somevar earlier in the python code somewhere, it will get updated once I call the set_variable_value function. If I use globals(), I can modify any global variable from that function.

To call it, we have to pass in 2 params in the js code rather than 3 defined, because cefpython will use up one param.

case 87: //w
  keyW = false;
  external.test_multiple_callbacks(js_callback_2);
  external.set_variable_value('hello','world'); // <-- notice param count
  break;

Result is as intended, enter image description here

Md. Abu Taher
  • 17,395
  • 5
  • 49
  • 73
  • When you call bindings.SetProperty in Python, it works. But the part I'm struggling with is trying to define the Python variable from the JavaScript (cefpython) side, which doesn't seem to update again once the script has initially run, or at least it's not happening when I call any of the functions I've tried so far. (And a lot of times, it just produces various errors. But you can still see the first instance outputted in the popup.) – C. Poe Apr 14 '18 at 23:05
  • added a solution, took me some time to experiment it :D – Md. Abu Taher Apr 15 '18 at 00:20
0

It would have helped if you pasted line 118:

py_callback("String sent from Javascript 89JX");

The problem is that there is no such function binded to javascript on the Python side. You have to define such function in Python and bind it in set_javascript_bindings function by calling bindings.SetFunction.

Czarek Tomczak
  • 20,079
  • 5
  • 49
  • 56