shevron
shevron

Reputation: 3663

PySide, QtWebKit and JavaScript argument types

I'm trying to call a Python function, defined as a Slot from JavaScript code running inside a QWebView instance. I've been able to set everything up, more or less, but for the life of me can't figure out (or find any documentation) on the types of arguments passed to the Python-implemented Slot when the function is called from within JavaScript with complex arguments (e.g. an array, JSON object or a JavaScript object such as an ErrorEvent object).

The following code is a bit complex but demonstrates the problem:

import sys
from PySide.QtGui import QApplication
from PySide.QtCore import QObject, Slot
from PySide.QtWebKit import QWebPage, QWebView, QWebElement


class JavascriptApi(QObject):

    OBJ_NAME    = 'MyApi'
    JS_API_INIT = ("window.addEventListener('error', function(e) { "
                   "    %(objname)s.log_error([e.message, e.filename, e.lineno, e.column]); "
                   "}, false);"
                   "window.addEventListener('load', %(objname)s.window_loaded, false);"
                   "console.log('JavaScript API initialized');") % {"objname": OBJ_NAME}

    def initialize(self, page):
        f = page.mainFrame()
        f.addToJavaScriptWindowObject(self.OBJ_NAME, self)
        f.evaluateJavaScript(self.JS_API_INIT)

    @Slot(list)
    def log_error(self, error):
        print error
        #print "JavaScript Error: %s [%s:%d:%d]" % (message, filename, lineno, column)

    @Slot(QWebElement)
    def window_loaded(self, window):
        print "JavaScript window.onload event reported"


class InstrumentedWebPage(QWebPage):

    def __init__(self, *args, **kwargs):
        super(InstrumentedWebPage, self).__init__(*args, **kwargs)
        self._js_api = JavascriptApi()
        self.mainFrame().javaScriptWindowObjectCleared.connect(self._init_javascript_api)

    def javaScriptConsoleMessage(self, message, lineNumber, sourceID):
        print "JavaScript Console Message: %s (%s:%d)" % (message, sourceID, lineNumber)

    def _finished(self, status):
        print "Finished!"
        QApplication.instance().quit()

    def _init_javascript_api(self):
        self._js_api.initialize(self)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    view = QWebView()
    view.setPage(InstrumentedWebPage())
    view.setHtml('<html><body>This is some HTML'
                 '<script>foo_bar("syntax error here);</script>'
                 '</body></html>')
    view.show()
    sys.exit(app.exec_())

If you run it, you will notice that the following message is logged to the console:

TypeError: incompatible type of argument(s) in call to log_error(); candidates were
    log_error(PyObject) (undefined:0)

So what would be the right type to use in the @Slot() for log_error? BTW the whole reason for using an intermediate JavaScript function between window.onerror and my Python code is for the same reason - window.onerror calls my code with an ErrorEvent JS object, and the only way I could catch it was with a @Slot(QWebElement), but then I couldn't figure out how to access the object properties.

Upvotes: 2

Views: 1268

Answers (2)

Jan Benes
Jan Benes

Reputation: 542

I have also been trying to figure out how to pass complex types from JavaScript to Python/PySide/QWebView and it seems serialization to JSON is the way to go.

On the JavaScript side:

window.addEventListener('error',
    function(e) { 
        MyApi.log_error(JSON.stringify(e)); // serialize the whole object e
    },                                      // to JSON (or create a new one if
    false);                                 // you just need a subset)

And on the Python side:

import json
...
class JavascriptApi(QObject):
    @Slot(str) # receive the serialized data as a string
    def log_error(self, error):
       data = json.loads(message) # deserialize the JSON on the python side
                                  # for most complex type, you'll get a dictionary
                                  # or an array
       print data

This method can also be employed in the opposite direction, see the first link above.

Upvotes: 1

justengel
justengel

Reputation: 6320

I think it just takes standard types like str, int, float. If it was a list it might be easier to give a str and convert it to a list in your python code. If the object you are trying to pass was inherited from QObject you can use QObject as the parameter. Qt has a lot of discussion about QVariant, but I think they got rid of that for PySide. http://qt-project.org/doc/qt-4.8/qtwebkit-bridge.html.

Just found that QByteArray will send the list, but as a byte array where you will have to convert it to a list in python. You might be able to use the QEvent.

class Communicator(QtGui.QWidget):
    @QtCore.Slot(QtCore.QByteArray)
    def do_something(self, my_list):
        print(my_list) # You will still have to find a , and split to make a list

    @QtCore.Slot(QtCore.QObject)
    def do_something2(self, my_class):
        print(my_class)

Upvotes: 0

Related Questions