2

I'm creating a simple QWebEngineView where I'm trying to retrieve a string by calling a js function, however I haven't found a way doing so.

This is a working example with just calling a js function

from PySide2.QtWebEngineWidgets import QWebEngineView


class View:
    def __init__(self):
       self.view = QWebEngineView()
       self.view.load(QUrl("https://mytestpage.com"))
       self.view.show()


def callback(a):
    print a
if __name__ == '__main__': 
    view = View()
    view.view.page().runJavaScript("window.getMail()", callback)

getMail is executed on the browser, however according to the doc's, to get the result, I would need to pass in a callback function as a second argument, however doing so yields a:

TypeError: `runJavaScript() takes exactly one argument (2 given) # 
user1767754
  • 23,311
  • 18
  • 141
  • 164

1 Answers1

2

A possible solution is to inject a QObject that allows to communicate with the DOM using Qt WebChannel:

import os
import sys
from PySide2 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
from jinja2 import Template


class Element(QtCore.QObject):
    loaded = QtCore.Signal()

    def __init__(self, name, parent=None):
        super(Element, self).__init__(parent)
        self._name = name
        self._is_loaded = False

    @property
    def name(self):
        return self._name

    @property
    def is_loaded(self):
        return self._is_loaded

    @QtCore.Slot()
    def set_loaded(self):
        self._is_loaded = True
        self.loaded.emit()

    def render_script(self, script, **kwargs):
        kwargs["name"] = self.name
        return Template(script).render(**kwargs)


class TestObject(Element):
    @QtCore.Slot(str)
    def test(self, res):
        print(res)


class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
    def __init__(self, *args, **kwargs):
        super(WebEnginePage, self).__init__(*args, **kwargs)
        self.loadFinished.connect(self.onLoadFinished)
        self._objects = []

    def add_object(self, obj):
        self._objects.append(obj)

    @QtCore.Slot(bool)
    def onLoadFinished(self, ok):
        if ok:
            self.load_qwebchannel()
            self.load_objects()

    def load_qwebchannel(self):
        file = QtCore.QFile(":/qtwebchannel/qwebchannel.js")
        if file.open(QtCore.QIODevice.ReadOnly):
            content = file.readAll()
            file.close()
            self.runJavaScript(content.data().decode())
        if self.webChannel() is None:
            channel = QtWebChannel.QWebChannel(self)
            self.setWebChannel(channel)

    def load_objects(self):
        if self.webChannel() is not None:
            objects = {obj.name: obj for obj in self._objects}
            self.webChannel().registerObjects(objects)
            _script = r"""
            {% for obj in objects %}
            var {{obj}} = null;
            {% endfor %}
            new QWebChannel(qt.webChannelTransport, function (channel) {
                {% for obj in objects %}
                    {{obj}} = channel.objects.{{obj}};
                    {{obj}}.set_loaded()
                {% endfor %}
            }); 
            """
            self.runJavaScript(Template(_script).render(objects=objects.keys()))


class WebPage(QtWebEngineWidgets.QWebEngineView):
    def __init__(self, parent=None):
        super().__init__(parent)

        page = WebEnginePage(self)
        self.setPage(page)

        self.test_object = TestObject("test_object", self)
        self.test_object.loaded.connect(self.test_object_loaded)
        page.add_object(self.test_object)

        self.load(QtCore.QUrl("https://mytestpage.com"))

    @QtCore.Slot()
    def test_object_loaded(self):
        script = self.test_object.render_script(
            r"""
        {{name}}.test(window.getMail());
        """
        )
        self.page().runJavaScript(script)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    web = WebPage()
    web.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • I've realized that the issue is: https://bugreports.qt.io/browse/PYSIDE-643 The reporter talks that he is aware of a work around ` by getting JavaScript to create a console message, which can then be filtered by overriding QWebEnginePage.consoleMessage` Are you aware of that? – user1767754 May 25 '20 at 04:24
  • @user1767754 The bug to point out is historical, using consoleMessage can be an option but a little dirty, I prefer my solution. – eyllanesc May 25 '20 at 04:30
  • I've just tested it out and it works really great. Just out of curiosity, if I wouldn't have exposed the function `getMail` as a function but just as a html `

    ` tag, would that make stuff easier or would I still need to run a `js` command to read that? And am I safe shipping this in production?

    – user1767754 May 25 '20 at 04:43
  • @user1767754 I do not understand you. – eyllanesc May 25 '20 at 04:44
  • `getMail` is the function that I'm calling on my webapp, I was wondering (if this makes sense) if I just had exposed the information that is in the `getMail` function as a plain text html field. If that would still need `runJavaScript` to access the html element. Sorry if I'm not very explicit with my wording. – user1767754 May 25 '20 at 04:49
  • @user1767754 I still do not understand anything, when you write your question well then I can just give you an answer, bye. On the other hand, the comments only serve to discuss the answers and as my answer worked then I will continue on my way. – eyllanesc May 25 '20 at 04:52
  • Allright, then one last thing. This will not work, when the `QApplication` was already initialized? Because I'm running this in a host application, and the call to QApplication was already invoked. – user1767754 May 25 '20 at 04:56
  • @user1767754 mmmm, if you already called QApplication then just create`web`: `web = WebPage()``web.show()` – eyllanesc May 25 '20 at 04:58
  • Perfect, I had thought that I would need to have `--remote-debugging-port=8000` delivered. Enjoy your Coffee! – user1767754 May 25 '20 at 05:21