PySide2 does not provide all of the overload methods of runJavaScript so it does not support passing a callback to it. A possible workaround is to use QtWebChannel that through websockets implements the communication between javascript and python:
import sys
import os
from PySide2 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class Backend(QtCore.QObject):
htmlChanged = QtCore.Signal()
def __init__(self, parent=None):
super(Backend, self).__init__(parent)
self._html = ""
@QtCore.Slot(str)
def toHtml(self, html):
self._html = html
self.htmlChanged.emit()
@property
def html(self):
return self._html
class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
def __init__(self, parent=None):
super(WebEnginePage, self).__init__(parent)
self.loadFinished.connect(self.onLoadFinished)
self._backend = Backend()
self.backend.htmlChanged.connect(self.handle_htmlChanged)
@property
def backend(self):
return self._backend
@QtCore.Slot(bool)
def onLoadFinished(self, ok):
if ok:
self.load_qwebchannel()
self.load_object()
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_object(self):
if self.webChannel() is not None:
self.webChannel().registerObject("backend", self.backend)
script = r"""
new QWebChannel(qt.webChannelTransport, function (channel) {
var backend = channel.objects.backend;
var html = document.getElementsByTagName('html')[0].innerHTML;
backend.toHtml(html);
});"""
self.runJavaScript(script)
def handle_htmlChanged(self):
print(self.backend.html)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
filename = os.path.join(CURRENT_DIR, "index.html")
url = QtCore.QUrl.fromLocalFile(filename)
page = WebEnginePage()
view = QtWebEngineWidgets.QWebEngineView()
page.load(url)
view.setPage(page)
view.resize(640, 480)
view.show()
sys.exit(app.exec_())
My previous logic focuses only on obtaining the HTML but in this part of the answer I will try to generalize the logic to be able to associate callbacks. The idea is to send the response to the bridge object associating a uuid that is related to the callback, the message must be sent in json format to be able to handle different types of data.
import json
import os
import sys
from PySide2 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
from jinja2 import Template
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class Bridge(QtCore.QObject):
initialized = QtCore.Signal()
def __init__(self, parent=None):
super().__init__(parent)
self._callbacks = dict()
@property
def callbacks(self):
return self._callbacks
@QtCore.Slot()
def init(self):
self.initialized.emit()
@QtCore.Slot(str, str)
def send(self, uuid, data):
res = json.loads(data)
callback = self.callbacks.pop(uuid, None)
if callable(callable):
callback(res)
class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
def __init__(self, parent=None):
super(WebEnginePage, self).__init__(parent)
self.loadFinished.connect(self.onLoadFinished)
self._bridge = Bridge()
@property
def bridge(self):
return self._bridge
@QtCore.Slot(bool)
def onLoadFinished(self, ok):
if ok:
self.load_qwebchannel()
self.load_object()
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_object(self):
if self.webChannel() is not None:
self.webChannel().registerObject("bridge", self.bridge)
script = r"""
var bridge = null;
new QWebChannel(qt.webChannelTransport, function (channel) {
bridge = channel.objects.bridge;
bridge.init();
});"""
self.runJavaScript(script)
def execute(self, code, callback, uuid=""):
uuid = uuid or QtCore.QUuid.createUuid().toString()
self.bridge.callbacks[uuid] = callback
script = Template(code).render(uuid=uuid)
self.runJavaScript(script)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.page = WebEnginePage()
self.view = QtWebEngineWidgets.QWebEngineView()
self.view.setPage(self.page)
self.page.bridge.initialized.connect(self.handle_initialized)
self.setCentralWidget(self.view)
filename = os.path.join(CURRENT_DIR, "index.html")
url = QtCore.QUrl.fromLocalFile(filename)
self.view.load(url)
def handle_initialized(self):
self.page.execute(
"""
var value = document.getElementsByTagName('html')[0].innerHTML
bridge.send('{{uuid}}', JSON.stringify(value));
""",
callbackfunction,
)
def callbackfunction(html):
print(html)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())