I want to access QtObject from HTML-page running in WebView - invoke methods, read/write properties, etc.
As far as I understood, I need to establish WebSockets connection between QML and HTML sides, and then use it as a transport for WebChannel.
Don't confuse WebView with WebEngineView - I know how to do it with WebEngineView, but I need to do it with WebView.
So, here's what I have.
QML side
QtObject {
id: someObject
WebChannel.id: "backend"
property string someProperty: “property value"
}
WebSocketServer {
listen: true
port: 55222
onClientConnected: {
console.log(webSocket.status);
//webSocket.onTextMessageReceived.connect(function(message) {
// console.log(qsTr("Server received message: %1").arg(message));
//});
}
}
WebView {
url: "index.html"
//webChannel: channel // invalid property name "webChannel"
//experimental.webChannel.registeredObjects: [someObject] // invalid property name "experimental"
}
WebChannel {
id: channel
registeredObjects: [someObject]
}
HTML side
window.onload = function()
{
// here will be QtObject from QML side
var backend;
var socket = new WebSocket("ws://localhost:55222");
socket.onopen = function()
{
//socket.send("some message");
new QWebChannel(socket, function(channel) {
backend = channel.objects.backend;
});
};
}
function alertProperty()
{
alert(backend.someProperty);
}
Simple message exchange works fine (socket.send()
), so transport is okay, but how do I assign WebChannel to WebView? With WebEngineView it was simple, there is a webChannel
property there (and there is even no need in using WebSockets), but there is nothing alike in WebView. I mean, something has to tell WebView that WebChannel contains my QtObject so it would be visible to JS?
And if WebView does not support WebChannel(?), how to do it with external browser then? This example shows that it is possible with C++, but I want to do it with QML.
I use Qt 5.11.1.
WebView
does not support WebChannel
by default. So the solution is to use WebSocketServer
with QWebChannelAbstractTransport
as I show below:
main.cpp
#include <QGuiApplication>
#include <QJsonDocument>
#include <QQmlApplicationEngine>
#include <QWebChannelAbstractTransport>
#include <QtWebView>
class WebSocketTransport : public QWebChannelAbstractTransport{
Q_OBJECT
public:
using QWebChannelAbstractTransport::QWebChannelAbstractTransport;
Q_INVOKABLE void sendMessage(const QJsonObject &message) override{
QJsonDocument doc(message);
emit messageChanged(QString::fromUtf8(doc.toJson(QJsonDocument::Compact)));
}
Q_INVOKABLE void textMessageReceive(const QString &messageData){
QJsonParseError error;
QJsonDocument message = QJsonDocument::fromJson(messageData.toUtf8(), &error);
if (error.error) {
qWarning() << "Failed to parse text message as JSON object:" << messageData
<< "Error is:" << error.errorString();
return;
} else if (!message.isObject()) {
qWarning() << "Received JSON message that is not an object: " << messageData;
return;
}
emit messageReceived(message.object(), this);
}
signals:
void messageChanged(const QString & message);
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<WebSocketTransport>("com.eyllanesc.org", 1, 0, "WebSocketTransport");
QtWebView::initialize();
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtWebSockets 1.1
import QtWebView 1.1
import QtWebChannel 1.0
import com.eyllanesc.org 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
WebView {
url: "qrc:/index.html"
anchors.fill: parent
}
QtObject {
id: someObject
property string someProperty: "prop"
WebChannel.id: "core"
function receiveText(text){
console.log("receiveText: ", text)
}
signal sendText(string text)
}
WebSocketTransport{
id: transport
}
WebSocketServer {
listen: true
port: 12345
onClientConnected: {
if(webSocket.status === WebSocket.Open){
channel.connectTo(transport)
webSocket.onTextMessageReceived.connect(transport.textMessageReceive)
transport.onMessageChanged.connect(webSocket.sendTextMessage)
}
}
}
WebChannel {
id: channel
registeredObjects: [someObject]
}
// testing
Timer{
interval: 500
running: true
repeat: true
onTriggered: someObject.sendText(Qt.formatTime(new Date(), "hh:mm:ss") + " from QML")
}
}
index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript">
//BEGIN SETUP
function output(message) {
var output = document.getElementById("output");
output.innerHTML = output.innerHTML + message + "\n";
}
window.onload = function() {
if (location.search != "")
var baseUrl = (/[?&]webChannelBaseUrl=([A-Za-z0-9\-:/\.]+)/.exec(location.search)[1]);
else
var baseUrl = "ws://localhost:12345";
output("Connecting to WebSocket server at " + baseUrl + ".");
var socket = new WebSocket(baseUrl);
socket.onclose = function() {
console.error("web channel closed");
};
socket.onerror = function(error) {
console.error("web channel error: " + error);
};
socket.onopen = function() {
output("WebSocket connected, setting up QWebChannel.");
new QWebChannel(socket, function(channel) {
// make core object accessible globally
window.core = channel.objects.core;
input.innerHTML = core.someProperty;
document.getElementById("send").onclick = function() {
var input = document.getElementById("input");
var text = input.value;
if (!text) {
return;
}
output("Sent message: " + text );
input.value = "";
core.receiveText(text + " From HTML");
}
core.sendText.connect(function(message) {
output("Received message-" + core.someProperty + " : " + message);
});
core.receiveText("Client connected, ready to send/receive messages!");
output("Connected to WebChannel, ready to send/receive messages!");
});
}
}
//END SETUP
</script>
<style type="text/css">
html {
height: 100%;
width: 100%;
}
#input {
width: 400px;
margin: 0 10px 0 0;
}
#send {
width: 90px;
margin: 0;
}
#output {
width: 500px;
height: 300px;
}
</style>
</head>
<body>
<textarea id="output"></textarea><br />
<input id="input" /><input type="submit" id="send" value="Send" onclick="javascript:click();" />
</body>
</html>
The complete example can be found in the following link