summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMilian Wolff <milian.wolff@kdab.com>2014-07-10 00:05:28 +0200
committerMilian Wolff <milian.wolff@kdab.com>2014-07-29 13:58:04 +0200
commit94912cf26ba70a2cadd23f1c931395be64c11eac (patch)
tree28cfa1d98bbcc7eeccba6de1e3317e30add56a2f
parent77a4c34bb0f3b8cc3107f9fc6be0f5f7b23bb9d6 (diff)
downloadqtwebchannel-94912cf26ba70a2cadd23f1c931395be64c11eac.tar.gz
Refactor and streamline API and IPC protocol.
This patch removes the obsolete API support to send raw messages using a QWebChannel. Instead, it is encouraged to directly use WebSockets or navigator.qt. By doing so, we can cleanup the code considerably. While at it, the transport API is adapted to work on QJsonObject messages, instead of QStrings. This will allow us to use more efficient formats in e.g. QtWebKit or QtWebEngine. One could also implement a JSONRPC interface using a custom transport then. Change-Id: Ia8c125a5558507b3cbecf128a46b19fdb013f47b Reviewed-by: Allan Sandfeld Jensen <allan.jensen@digia.com>
-rw-r--r--examples/standalone/websockettransport.cpp24
-rw-r--r--examples/standalone/websockettransport.h5
-rw-r--r--src/webchannel/qmetaobjectpublisher.cpp140
-rw-r--r--src/webchannel/qmetaobjectpublisher_p.h33
-rw-r--r--src/webchannel/qqmlwebchannel.h4
-rw-r--r--src/webchannel/qwebchannel.cpp31
-rw-r--r--src/webchannel/qwebchannel.h2
-rw-r--r--src/webchannel/qwebchannel.js218
-rw-r--r--src/webchannel/qwebchannel_p.h2
-rw-r--r--src/webchannel/qwebchannelabstracttransport.h5
-rw-r--r--tests/auto/qml/Client.qml24
-rw-r--r--tests/auto/qml/testtransport.cpp24
-rw-r--r--tests/auto/qml/testtransport.h6
-rw-r--r--tests/auto/qml/tst_metaobjectpublisher.qml320
-rw-r--r--tests/auto/qml/tst_multiclient.qml13
-rw-r--r--tests/auto/qml/tst_webchannel.qml242
-rw-r--r--tests/auto/webchannel/tst_webchannel.cpp6
-rw-r--r--tests/auto/webchannel/tst_webchannel.h2
18 files changed, 499 insertions, 602 deletions
diff --git a/examples/standalone/websockettransport.cpp b/examples/standalone/websockettransport.cpp
index 5df9f85..f1ecee2 100644
--- a/examples/standalone/websockettransport.cpp
+++ b/examples/standalone/websockettransport.cpp
@@ -41,6 +41,10 @@
#include "websockettransport.h"
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QDebug>
+
#include <QtWebSockets/QWebSocket>
/*!
@@ -66,9 +70,25 @@ WebSocketTransport::~WebSocketTransport()
}
-void WebSocketTransport::sendTextMessage(const QString &message)
+void WebSocketTransport::sendMessage(const QJsonObject &message)
+{
+ QJsonDocument doc(message);
+ m_socket->sendTextMessage(QString::fromUtf8(doc.toJson(QJsonDocument::Compact)));
+}
+
+void WebSocketTransport::textMessageReceived(const QString &messageData)
{
- m_socket->sendTextMessage(message);
+ 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);
}
QT_END_NAMESPACE
diff --git a/examples/standalone/websockettransport.h b/examples/standalone/websockettransport.h
index 5fab8f6..828ac00 100644
--- a/examples/standalone/websockettransport.h
+++ b/examples/standalone/websockettransport.h
@@ -54,7 +54,10 @@ public:
explicit WebSocketTransport(QWebSocket *socket);
virtual ~WebSocketTransport();
- void sendTextMessage(const QString &message) Q_DECL_OVERRIDE;
+ void sendMessage(const QJsonObject &message) Q_DECL_OVERRIDE;
+
+private Q_SLOTS:
+ void textMessageReceived(const QString &message);
private:
QWebSocket *m_socket;
diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp
index 646398d..d9aa548 100644
--- a/src/webchannel/qmetaobjectpublisher.cpp
+++ b/src/webchannel/qmetaobjectpublisher.cpp
@@ -59,30 +59,11 @@ QT_BEGIN_NAMESPACE
namespace {
-// NOTE: keep in sync with corresponding maps in qwebchannel.js and WebChannelTest.qml
-enum Type {
- TypeInvalid = 0,
-
- TYPES_FIRST_VALUE = 1,
-
- TypeSignal = 1,
- TypePropertyUpdate = 2,
- TypeInit = 3,
- TypeIdle = 4,
- TypeDebug = 5,
- TypeInvokeMethod = 6,
- TypeConnectToSignal = 7,
- TypeDisconnectFromSignal = 8,
- TypeSetProperty = 9,
-
- TYPES_LAST_VALUE = 9
-};
-
-Type toType(const QJsonValue &value)
+MessageType toType(const QJsonValue &value)
{
int i = value.toInt(-1);
if (i >= TYPES_FIRST_VALUE && i <= TYPES_LAST_VALUE) {
- return static_cast<Type>(i);
+ return static_cast<MessageType>(i);
} else {
return TypeInvalid;
}
@@ -99,13 +80,11 @@ const QString KEY_OBJECT = QStringLiteral("object");
const QString KEY_DESTROYED = QStringLiteral("destroyed");
const QString KEY_SIGNAL = QStringLiteral("signal");
const QString KEY_TYPE = QStringLiteral("type");
-const QString KEY_MESSAGE = QStringLiteral("message");
const QString KEY_METHOD = QStringLiteral("method");
const QString KEY_ARGS = QStringLiteral("args");
const QString KEY_PROPERTY = QStringLiteral("property");
const QString KEY_VALUE = QStringLiteral("value");
-
QString objectId(const QObject *object)
{
return QString::number(quintptr(object), 16);
@@ -261,7 +240,10 @@ void QMetaObjectPublisher::initializeClients()
objectInfos[it.key()] = info;
}
}
- webChannel->sendMessage(TypeInit, objectInfos);
+ QJsonObject message;
+ message[KEY_TYPE] = TypeInit;
+ message[KEY_DATA] = objectInfos;
+ broadcastMessage(message);
propertyUpdatesInitialized = true;
pendingInit = false;
}
@@ -334,31 +316,34 @@ void QMetaObjectPublisher::sendPendingPropertyUpdates()
}
pendingPropertyUpdates.clear();
- webChannel->sendMessage(TypePropertyUpdate, data);
+ QJsonObject message;
+ message[KEY_TYPE] = TypePropertyUpdate;
+ message[KEY_DATA] = data;
+ broadcastMessage(message);
setClientIsIdle(false);
}
-QByteArray QMetaObjectPublisher::invokeMethod(QObject *const object, const int methodIndex,
- const QJsonArray &args, const QJsonValue &id)
+QJsonValue QMetaObjectPublisher::invokeMethod(QObject *const object, const int methodIndex,
+ const QJsonArray &args)
{
const QMetaMethod &method = object->metaObject()->method(methodIndex);
if (method.name() == QByteArrayLiteral("deleteLater")) {
// invoke `deleteLater` on wrapped QObject indirectly
deleteWrappedObject(object);
- return QByteArray();
+ return QJsonValue();
} else if (!method.isValid()) {
qWarning() << "Cannot invoke unknown method of index" << methodIndex << "on object" << object << '.';
- return QByteArray();
+ return QJsonValue();
} else if (method.access() != QMetaMethod::Public) {
qWarning() << "Cannot invoke non-public method" << method.name() << "on object" << object << '.';
- return QByteArray();
+ return QJsonValue();
} else if (method.methodType() != QMetaMethod::Method && method.methodType() != QMetaMethod::Slot) {
qWarning() << "Cannot invoke non-public method" << method.name() << "on object" << object << '.';
- return QByteArray();
+ return QJsonValue();
} else if (args.size() > 10) {
qWarning() << "Cannot invoke method" << method.name() << "on object" << object << "with more than 10 arguments, as that is not supported by QMetaMethod::invoke.";
- return QByteArray();
+ return QJsonValue();
} else if (args.size() > method.parameterCount()) {
qWarning() << "Ignoring additional arguments while invoking method" << method.name() << "on object" << object << ':'
<< args.size() << "arguments given, but method only takes" << method.parameterCount() << '.';
@@ -389,8 +374,7 @@ QByteArray QMetaObjectPublisher::invokeMethod(QObject *const object, const int m
arguments[0], arguments[1], arguments[2], arguments[3], arguments[4],
arguments[5], arguments[6], arguments[7], arguments[8], arguments[9]);
- // and send the return value to the client
- return generateJSONMessage(id, wrapResult(returnValue), true);
+ return wrapResult(returnValue);
}
void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments)
@@ -399,11 +383,11 @@ void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signal
return;
}
if (!signalToPropertyMap.value(object).contains(signalIndex)) {
- QJsonObject data;
+ QJsonObject message;
const QString &objectName = registeredObjectIds.value(object);
Q_ASSERT(!objectName.isEmpty());
- data[KEY_OBJECT] = objectName;
- data[KEY_SIGNAL] = signalIndex;
+ message[KEY_OBJECT] = objectName;
+ message[KEY_SIGNAL] = signalIndex;
if (!arguments.isEmpty()) {
// TODO: wrap (new) objects on the fly
QJsonArray args;
@@ -419,9 +403,10 @@ void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signal
#else
args = QJsonArray::fromVariantList(arguments);
#endif
- data[KEY_ARGS] = args;
+ message[KEY_ARGS] = args;
}
- webChannel->sendMessage(TypeSignal, data);
+ message[KEY_TYPE] = TypeSignal;
+ broadcastMessage(message);
if (signalIndex == s_destroyedSignalIndex) {
objectDestroyed(object);
@@ -486,78 +471,71 @@ void QMetaObjectPublisher::deleteWrappedObject(QObject *object) const
object->deleteLater();
}
-QByteArray QMetaObjectPublisher::handleRequest(const QJsonObject &message)
+void QMetaObjectPublisher::broadcastMessage(const QJsonObject &message) const
{
- if (!message.contains(KEY_DATA)) {
- return QByteArray();
+ if (webChannel->d_func()->transports.isEmpty()) {
+ qWarning("QWebChannel is not connected to any transports, cannot send messages.");
+ return;
+ }
+
+ foreach (QWebChannelAbstractTransport *transport, webChannel->d_func()->transports) {
+ transport->sendMessage(message);
}
+}
- const QJsonObject &payload = message.value(KEY_DATA).toObject();
- if (!payload.contains(KEY_TYPE)) {
- return QByteArray();
+void QMetaObjectPublisher::handleMessage(const QJsonObject &message, QWebChannelAbstractTransport *transport)
+{
+ if (!message.contains(KEY_TYPE)) {
+ qWarning("JSON message object is missing the type property: %s", QJsonDocument(message).toJson().constData());
+ return;
}
- const Type type = toType(payload.value(KEY_TYPE));
+ const MessageType type = toType(message.value(KEY_TYPE));
if (type == TypeIdle) {
setClientIsIdle(true);
- return QByteArray();
} else if (type == TypeInit) {
if (!blockUpdates) {
initializeClients();
} else {
pendingInit = true;
}
- return QByteArray();
} else if (type == TypeDebug) {
static QTextStream out(stdout);
- out << "DEBUG: " << payload.value(KEY_MESSAGE).toString() << endl;
- return QByteArray();
- } else if (payload.contains(KEY_OBJECT)) {
- const QString &objectName = payload.value(KEY_OBJECT).toString();
+ out << "DEBUG: " << message.value(KEY_DATA).toString() << endl;
+ } else if (message.contains(KEY_OBJECT)) {
+ const QString &objectName = message.value(KEY_OBJECT).toString();
QObject *object = registeredObjects.value(objectName);
if (!object) {
qWarning() << "Unknown object encountered" << objectName;
- return QByteArray();
+ return;
}
if (type == TypeInvokeMethod) {
- return invokeMethod(object, payload.value(KEY_METHOD).toInt(-1), payload.value(KEY_ARGS).toArray(), message.value(KEY_ID));
+ if (!message.contains(KEY_ID)) {
+ qWarning("JSON message object is missing the id property: %s",
+ QJsonDocument(message).toJson().constData());
+ return;
+ }
+ QJsonObject response;
+ response[KEY_TYPE] = TypeResponse;
+ response[KEY_ID] = message.value(KEY_ID);
+ response[KEY_DATA] = invokeMethod(object, message.value(KEY_METHOD).toInt(-1), message.value(KEY_ARGS).toArray());
+ transport->sendMessage(response);
} else if (type == TypeConnectToSignal) {
- signalHandler.connectTo(object, payload.value(KEY_SIGNAL).toInt(-1));
- return QByteArray();
+ signalHandler.connectTo(object, message.value(KEY_SIGNAL).toInt(-1));
} else if (type == TypeDisconnectFromSignal) {
- signalHandler.disconnectFrom(object, payload.value(KEY_SIGNAL).toInt(-1));
- return QByteArray();
+ signalHandler.disconnectFrom(object, message.value(KEY_SIGNAL).toInt(-1));
} else if (type == TypeSetProperty) {
- const int propertyIdx = payload.value(KEY_PROPERTY).toInt(-1);
+ const int propertyIdx = message.value(KEY_PROPERTY).toInt(-1);
QMetaProperty property = object->metaObject()->property(propertyIdx);
if (!property.isValid()) {
- qWarning() << "Cannot set unknown property" << payload.value(KEY_PROPERTY) << "of object" << objectName;
- return QByteArray();
- } else if (!object->metaObject()->property(propertyIdx).write(object, payload.value(KEY_VALUE).toVariant())) {
- qWarning() << "Could not write value " << payload.value(KEY_VALUE)
+ qWarning() << "Cannot set unknown property" << message.value(KEY_PROPERTY) << "of object" << objectName;
+ } else if (!object->metaObject()->property(propertyIdx).write(object, message.value(KEY_VALUE).toVariant())) {
+ qWarning() << "Could not write value " << message.value(KEY_VALUE)
<< "to property" << property.name() << "of object" << objectName;
- return QByteArray();
}
- return QByteArray();
}
}
- return QByteArray();
-}
-
-void QMetaObjectPublisher::handleMessage(const QString &message)
-{
- QWebChannelAbstractTransport *transport = qobject_cast<QWebChannelAbstractTransport*>(sender());
- Q_ASSERT(transport);
-
- const QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8());
- if (!doc.isObject()) {
- return;
- }
- const QByteArray &response = handleRequest(doc.object());
- if (!response.isEmpty()) {
- transport->sendTextMessage(QString::fromUtf8(response));
- }
}
void QMetaObjectPublisher::setBlockUpdates(bool block)
diff --git a/src/webchannel/qmetaobjectpublisher_p.h b/src/webchannel/qmetaobjectpublisher_p.h
index 4da0c26..82afe57 100644
--- a/src/webchannel/qmetaobjectpublisher_p.h
+++ b/src/webchannel/qmetaobjectpublisher_p.h
@@ -54,7 +54,28 @@
QT_BEGIN_NAMESPACE
+// NOTE: keep in sync with corresponding maps in qwebchannel.js and WebChannelTest.qml
+enum MessageType {
+ TypeInvalid = 0,
+
+ TYPES_FIRST_VALUE = 1,
+
+ TypeSignal = 1,
+ TypePropertyUpdate = 2,
+ TypeInit = 3,
+ TypeIdle = 4,
+ TypeDebug = 5,
+ TypeInvokeMethod = 6,
+ TypeConnectToSignal = 7,
+ TypeDisconnectFromSignal = 8,
+ TypeSetProperty = 9,
+ TypeResponse = 10,
+
+ TYPES_LAST_VALUE = 10
+};
+
class QWebChannel;
+class QWebChannelAbstractTransport;
class Q_WEBCHANNEL_EXPORT QMetaObjectPublisher : public QObject
{
Q_OBJECT
@@ -75,11 +96,9 @@ public:
void registerObject(const QString &id, QObject *object);
/**
- * Handle the given WebChannel client request and potentially give a response.
- *
- * @return true if the request was handled, false otherwise.
+ * Send the given message to all known transports.
*/
- QByteArray handleRequest(const QJsonObject &message);
+ void broadcastMessage(const QJsonObject &message) const;
/**
* Serialize the QMetaObject of @p object and return it in JSON form.
@@ -125,7 +144,7 @@ public:
* The return value of the method invocation is then serialized and a response message
* is returned.
*/
- QByteArray invokeMethod(QObject *const object, const int methodIndex, const QJsonArray &args, const QJsonValue &id);
+ QJsonValue invokeMethod(QObject *const object, const int methodIndex, const QJsonArray &args);
/**
* Callback of the signalHandler which forwards the signal invocation to the webchannel clients.
@@ -164,9 +183,9 @@ Q_SIGNALS:
public Q_SLOTS:
/**
- * Parse the message as JSON and if it succeeds, call handleRequest with the obtained JSON object.
+ * Handle the @p message and if needed send a response to @p transport.
*/
- void handleMessage(const QString &message);
+ void handleMessage(const QJsonObject &message, QWebChannelAbstractTransport *transport);
protected:
void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE;
diff --git a/src/webchannel/qqmlwebchannel.h b/src/webchannel/qqmlwebchannel.h
index 3bbd2a0..96ffdd3 100644
--- a/src/webchannel/qqmlwebchannel.h
+++ b/src/webchannel/qqmlwebchannel.h
@@ -89,9 +89,9 @@ private:
static void transports_clear(QQmlListProperty<QObject> *prop);
};
+QT_END_NAMESPACE
+
QML_DECLARE_TYPE( QQmlWebChannel )
QML_DECLARE_TYPEINFO( QQmlWebChannel, QML_HAS_ATTACHED_PROPERTIES )
-QT_END_NAMESPACE
-
#endif // QQMLWEBCHANNEL_H
diff --git a/src/webchannel/qwebchannel.cpp b/src/webchannel/qwebchannel.cpp
index d0b968e..3aebb14 100644
--- a/src/webchannel/qwebchannel.cpp
+++ b/src/webchannel/qwebchannel.cpp
@@ -50,20 +50,6 @@
QT_BEGIN_NAMESPACE
-QByteArray generateJSONMessage(const QJsonValue &id, const QJsonValue &data, bool response)
-{
- QJsonObject obj;
- if (response) {
- obj[QStringLiteral("response")] = true;
- }
- obj[QStringLiteral("id")] = id;
- if (!data.isNull()) {
- obj[QStringLiteral("data")] = data;
- }
- QJsonDocument doc(obj);
- return doc.toJson(QJsonDocument::Compact);
-}
-
void QWebChannelPrivate::_q_transportDestroyed(QObject *object)
{
const int idx = transports.indexOf(static_cast<QWebChannelAbstractTransport*>(object));
@@ -144,7 +130,7 @@ void QWebChannel::connectTo(QWebChannelAbstractTransport *transport)
Q_ASSERT(transport);
if (!d->transports.contains(transport)) {
d->transports << transport;
- connect(transport, &QWebChannelAbstractTransport::textMessageReceived,
+ connect(transport, &QWebChannelAbstractTransport::messageReceived,
d->publisher, &QMetaObjectPublisher::handleMessage,
Qt::UniqueConnection);
connect(transport, SIGNAL(destroyed(QObject*)),
@@ -162,21 +148,6 @@ void QWebChannel::disconnectFrom(QWebChannelAbstractTransport *transport)
}
}
-void QWebChannel::sendMessage(const QJsonValue &id, const QJsonValue &data) const
-{
- Q_D(const QWebChannel);
- if (d->transports.isEmpty()) {
- qWarning("QWebChannel is not connected to any transports, cannot send messages.");
- return;
- }
-
- const QByteArray &message = generateJSONMessage(id, data, false);
- const QString &messageText = QString::fromUtf8(message);
- foreach (QWebChannelAbstractTransport *transport, d->transports) {
- transport->sendTextMessage(messageText);
- }
-}
-
QT_END_NAMESPACE
#include "moc_qwebchannel.cpp"
diff --git a/src/webchannel/qwebchannel.h b/src/webchannel/qwebchannel.h
index a65a4ee..46d77a3 100644
--- a/src/webchannel/qwebchannel.h
+++ b/src/webchannel/qwebchannel.h
@@ -97,8 +97,6 @@ public Q_SLOTS:
void connectTo(QWebChannelAbstractTransport *transport);
void disconnectFrom(QWebChannelAbstractTransport *transport);
- void sendMessage(const QJsonValue &id, const QJsonValue &data = QJsonValue()) const;
-
private:
Q_DECLARE_PRIVATE(QWebChannel)
QWebChannel(QWebChannelPrivate &dd, QObject *parent = 0);
diff --git a/src/webchannel/qwebchannel.js b/src/webchannel/qwebchannel.js
index dd6c4b3..3274d65 100644
--- a/src/webchannel/qwebchannel.js
+++ b/src/webchannel/qwebchannel.js
@@ -52,9 +52,12 @@ var QWebChannelMessageTypes = {
connectToSignal: 7,
disconnectFromSignal: 8,
setProperty: 9,
+ response: 10,
};
-var QWebChannel = function(baseUrlOrSocket, initCallback, rawChannel)
+// TODO: always expect an initialized transport object with a defined interface
+// to be passed in, remove automagic WebSocket code
+var QWebChannel = function(baseUrlOrSocket, initCallback)
{
var channel = this;
this.send = function(data)
@@ -64,33 +67,115 @@ var QWebChannel = function(baseUrlOrSocket, initCallback, rawChannel)
}
channel.socket.send(data);
}
- this.messageReceived = function(message)
+ this.onMessageReceived = function(message)
{
- var jsonData = JSON.parse(message.data);
- if (jsonData.id === undefined) {
- console.error("invalid message received:", message.data);
+ var data = message.data;
+ if (typeof data === "string") {
+ data = JSON.parse(data);
+ }
+ switch (data.type) {
+ case QWebChannelMessageTypes.signal:
+ channel.handleSignal(data);
+ break;
+ case QWebChannelMessageTypes.response:
+ channel.handleResponse(data);
+ break;
+ case QWebChannelMessageTypes.propertyUpdate:
+ channel.handlePropertyUpdate(data);
+ break;
+ case QWebChannelMessageTypes.init:
+ channel.handleInit(data);
+ break;
+ default:
+ console.error("invalid message received:", message.data);
+ break;
+ }
+ }
+
+ this.execCallbacks = {};
+ this.execId = 0;
+ this.exec = function(data, callback)
+ {
+ if (!callback) {
+ // if no callback is given, send directly
+ channel.send(data);
return;
}
- if (jsonData.data === undefined) {
- jsonData.data = {};
+ if (channel.execId === Number.MAX_VALUE) {
+ // wrap
+ channel.execId = Number.MIN_VALUE;
}
- if (jsonData.response) {
- channel.execCallbacks[jsonData.id](jsonData.data);
- delete channel.execCallbacks[jsonData.id];
- } else if (channel.subscriptions[jsonData.id]) {
- channel.subscriptions[jsonData.id].forEach(function(callback) {
- (callback)(jsonData.data); }
- );
+ if (data.hasOwnProperty("id")) {
+ console.error("Cannot exec message with property id: " + JSON.stringify(data));
+ return;
+ }
+ data.id = channel.execId++;
+ channel.execCallbacks[data.id] = callback;
+ channel.send(data);
+ };
+
+ this.objects = {};
+
+ this.handleSignal = function(message)
+ {
+ var object = channel.objects[message.object];
+ if (object) {
+ object.signalEmitted(message.signal, message.args);
+ } else {
+ console.warn("Unhandled signal: " + message.object + "::" + message.signal);
}
}
- this.initialized = function()
+ this.handleResponse = function(message)
{
- if (rawChannel) {
+ if (!message.hasOwnProperty("id")) {
+ console.error("Invalid response message received: ", JSON.stringify(message));
+ return;
+ }
+ channel.execCallbacks[message.id](message.data);
+ delete channel.execCallbacks[message.id];
+ }
+
+ this.handlePropertyUpdate = function(message)
+ {
+ for (var i in message.data) {
+ var data = message.data[i];
+ var object = channel.objects[data.object];
+ if (object) {
+ object.propertyUpdate(data.signals, data.properties);
+ } else {
+ console.warn("Unhandled property update: " + data.object + "::" + data.signal);
+ }
+ }
+ setTimeout(function() { channel.exec({type: QWebChannelMessageTypes.idle}); }, 0);
+ }
+
+ // prevent multiple initialization which might happen with multiple webchannel clients.
+ this.initialized = false;
+ this.handleInit = function(message)
+ {
+ if (channel.initialized) {
+ return;
+ }
+ channel.initialized = true;
+ for (var objectName in message.data) {
+ var data = message.data[objectName];
+ var object = new QObject(objectName, data, channel);
+ }
+ if (initCallback) {
initCallback(channel);
- } else {
- channel.initMetaObjectPublisher(initCallback);
}
+ setTimeout(function() { channel.exec({type: QWebChannelMessageTypes.idle}); }, 0);
+ }
+
+ this.debug = function(message)
+ {
+ channel.send({type: QWebChannelMessageTypes.debug, data: message});
+ };
+
+ this.onSocketReady = function(doneCallback)
+ {
+ channel.exec({type: QWebChannelMessageTypes.init});
}
if (typeof baseUrlOrSocket === 'object') {
@@ -99,13 +184,13 @@ var QWebChannel = function(baseUrlOrSocket, initCallback, rawChannel)
{
channel.socket.postMessage(data);
}
- this.socket.onmessage = this.messageReceived
- setTimeout(this.initialized, 0);
+ this.socket.onmessage = this.onMessageReceived
+ this.onSocketReady();
} else {
///TODO: use QWebChannel protocol, once custom protcols are supported by QtWebSocket
this.socket = new WebSocket(baseUrlOrSocket/*, "QWebChannel" */);
- this.socket.onopen = this.initialized
+ this.socket.onopen = this.onSocketReady
this.socket.onclose = function()
{
console.error("web channel closed");
@@ -114,96 +199,7 @@ var QWebChannel = function(baseUrlOrSocket, initCallback, rawChannel)
{
console.error("web channel error: " + error);
};
- this.socket.onmessage = this.messageReceived
- }
-
- this.subscriptions = {};
- this.subscribe = function(id, callback)
- {
- if (channel.subscriptions[id]) {
- channel.subscriptions[id].push(callback);
- } else {
- channel.subscriptions[id] = [callback];
- }
- };
-
- this.execCallbacks = {};
- this.execId = 0;
- this.exec = function(data, callback)
- {
- if (!callback) {
- // if no callback is given, send directly
- channel.send({data: data});
- return;
- }
- if (channel.execId === Number.MAX_VALUE) {
- // wrap
- channel.execId = Number.MIN_VALUE;
- }
- var id = channel.execId++;
- channel.execCallbacks[id] = callback;
- channel.send({"id": id, "data": data});
- };
-
- this.objects = {};
-
- this.initMetaObjectPublisher = function(doneCallback)
- {
- // prevent multiple initialization which might happen with multiple webchannel clients.
- var initialized = false;
-
- channel.subscribe(
- QWebChannelMessageTypes.signal,
- function(payload) {
- var object = channel.objects[payload.object];
- if (object) {
- object.signalEmitted(payload.signal, payload.args);
- } else {
- console.warn("Unhandled signal: " + payload.object + "::" + payload.signal);
- }
- }
- );
-
- channel.subscribe(
- QWebChannelMessageTypes.propertyUpdate,
- function(payload) {
- for (var i in payload) {
- var data = payload[i];
- var object = channel.objects[data.object];
- if (object) {
- object.propertyUpdate(data.signals, data.properties);
- } else {
- console.warn("Unhandled property update: " + data.object + "::" + data.signal);
- }
- }
- setTimeout(function() { channel.exec({type: QWebChannelMessageTypes.idle}); }, 0);
- }
- );
-
- channel.subscribe(
- QWebChannelMessageTypes.init,
- function(payload) {
- if (initialized) {
- return;
- }
- initialized = true;
- for (var objectName in payload) {
- var data = payload[objectName];
- var object = new QObject(objectName, data, channel);
- }
- if (doneCallback) {
- doneCallback(channel);
- }
- setTimeout(function() { channel.exec({type: QWebChannelMessageTypes.idle}); }, 0);
- }
- );
-
- channel.debug = function(message)
- {
- channel.send({"data" : {"type" : QWebChannelMessageTypes.debug, "message" : message}});
- };
-
- channel.exec({type: QWebChannelMessageTypes.init});
+ this.socket.onmessage = this.onMessageReceived
}
};
diff --git a/src/webchannel/qwebchannel_p.h b/src/webchannel/qwebchannel_p.h
index 62b2cd5..add55d1 100644
--- a/src/webchannel/qwebchannel_p.h
+++ b/src/webchannel/qwebchannel_p.h
@@ -53,8 +53,6 @@ class QJsonValue;
class QWebChannelAbstractTransport;
class QMetaObjectPublisher;
-Q_WEBCHANNEL_EXPORT QByteArray generateJSONMessage(const QJsonValue &id, const QJsonValue &data, bool response);
-
class Q_WEBCHANNEL_EXPORT QWebChannelPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QWebChannel)
diff --git a/src/webchannel/qwebchannelabstracttransport.h b/src/webchannel/qwebchannelabstracttransport.h
index 0062fab..a13851c 100644
--- a/src/webchannel/qwebchannelabstracttransport.h
+++ b/src/webchannel/qwebchannelabstracttransport.h
@@ -47,6 +47,7 @@
QT_BEGIN_NAMESPACE
+class QJsonObject;
class Q_WEBCHANNEL_EXPORT QWebChannelAbstractTransport : public QObject
{
Q_OBJECT
@@ -58,13 +59,13 @@ public Q_SLOTS:
/**
* Send a text @p message to the remote client.
*/
- virtual void sendTextMessage(const QString &message) = 0;
+ virtual void sendMessage(const QJsonObject &message) = 0;
Q_SIGNALS:
/**
* Emitted when a new text message was received from the remote client.
*/
- void textMessageReceived(const QString &message);
+ void messageReceived(const QJsonObject &message, QWebChannelAbstractTransport *transport);
};
QT_END_NAMESPACE
diff --git a/tests/auto/qml/Client.qml b/tests/auto/qml/Client.qml
index 9904fe8..d15fd58 100644
--- a/tests/auto/qml/Client.qml
+++ b/tests/auto/qml/Client.qml
@@ -68,15 +68,17 @@ Item {
console.log("client posts message: ", message);
}
clientMessages.push(message);
- serverTransport.textMessageReceived(message);
+ serverTransport.receiveMessage(message);
}
Component.onCompleted: {
- serverTransport.sendTextMessageRequested.connect(function(message) {
+ serverTransport.sendMessageRequested.connect(function(message) {
if (debug) {
console.log("client received message: ", message);
}
- onmessage({data:message});
+ if (onmessage) {
+ onmessage({data:message});
+ }
});
}
}
@@ -128,8 +130,11 @@ Item {
function awaitMessage()
{
var msg = awaitRawMessage()
+ if (debug) {
+ console.log("handling message: ", msg);
+ }
if (!msg) {
- return msg;
+ return false;
}
return JSON.parse(msg);
}
@@ -138,17 +143,15 @@ Item {
{
var msg = awaitMessage();
verify(msg);
- verify(msg.data);
- verify(msg.data.type);
- compare(msg.data.type, JSClient.QWebChannelMessageTypes.init);
+ verify(msg.type);
+ compare(msg.type, JSClient.QWebChannelMessageTypes.init);
}
function awaitIdle()
{
var msg = awaitMessage();
verify(msg);
- verify(msg.data);
- compare(msg.data.type, JSClient.QWebChannelMessageTypes.idle);
+ compare(msg.type, JSClient.QWebChannelMessageTypes.idle);
verify(webChannel.clientIsIdle())
}
@@ -158,8 +161,7 @@ Item {
do {
msg = awaitMessage();
verify(msg);
- verify(msg.data);
- } while (msg.data.type === JSClient.QWebChannelMessageTypes.idle);
+ } while (msg.type === JSClient.QWebChannelMessageTypes.idle);
return msg;
}
diff --git a/tests/auto/qml/testtransport.cpp b/tests/auto/qml/testtransport.cpp
index a332955..d73a4b8 100644
--- a/tests/auto/qml/testtransport.cpp
+++ b/tests/auto/qml/testtransport.cpp
@@ -41,6 +41,10 @@
#include "testtransport.h"
+#include <QDebug>
+#include <QJsonDocument>
+#include <QJsonObject>
+
QT_BEGIN_NAMESPACE
TestTransport::TestTransport(QObject *parent)
@@ -49,9 +53,25 @@ TestTransport::TestTransport(QObject *parent)
}
-void TestTransport::sendTextMessage(const QString &message)
+void TestTransport::sendMessage(const QJsonObject &message)
+{
+ emit sendMessageRequested(message);
+}
+
+void TestTransport::receiveMessage(const QString &message)
{
- emit sendTextMessageRequested(message);
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8(), &error);
+ if (error.error) {
+ qWarning("Failed to parse JSON message: %s\nError is: %s",
+ qPrintable(message), qPrintable(error.errorString()));
+ return;
+ } else if (!doc.isObject()) {
+ qWarning("Received JSON message that is not an object: %s",
+ qPrintable(message));
+ return;
+ }
+ emit messageReceived(doc.object(), this);
}
QT_END_NAMESPACE
diff --git a/tests/auto/qml/testtransport.h b/tests/auto/qml/testtransport.h
index dd07832..ed7eae9 100644
--- a/tests/auto/qml/testtransport.h
+++ b/tests/auto/qml/testtransport.h
@@ -52,10 +52,12 @@ class TestTransport : public QWebChannelAbstractTransport
public:
explicit TestTransport(QObject *parent = 0);
- virtual void sendTextMessage(const QString &message);
+ virtual void sendMessage(const QJsonObject &message) Q_DECL_OVERRIDE;
+
+ Q_INVOKABLE void receiveMessage(const QString &message);
Q_SIGNALS:
- void sendTextMessageRequested(const QString &message);
+ void sendMessageRequested(const QJsonObject &message);
};
QT_END_NAMESPACE
diff --git a/tests/auto/qml/tst_metaobjectpublisher.qml b/tests/auto/qml/tst_metaobjectpublisher.qml
deleted file mode 100644
index 9b2088c..0000000
--- a/tests/auto/qml/tst_metaobjectpublisher.qml
+++ /dev/null
@@ -1,320 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
-** Contact: http://www.qt-project.org/legal
-**
-** This file is part of the QtWebChannel module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and Digia. For licensing terms and
-** conditions see http://qt.digia.com/licensing. For further information
-** use the contact form at http://qt.digia.com/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 2.1 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 2.1 requirements
-** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
-**
-** In addition, as a special exception, Digia gives you certain additional
-** rights. These rights are described in the Digia Qt LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3.0 as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU General Public License version 3.0 requirements will be
-** met: http://www.gnu.org/copyleft/gpl.html.
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-import QtQuick 2.0
-import QtTest 1.0
-
-import QtWebChannel 1.0
-import QtWebChannel.Tests 1.0
-import "qrc:///qtwebchannel/qwebchannel.js" as JSClient
-
-TestCase {
- name: "MetaObjectPublisher"
-
- Client {
- id: client
- }
-
- property var lastMethodArg
-
- QtObject {
- id: myObj
- property int myProperty: 1
-
- signal mySignal(var arg)
-
- function myMethod(arg)
- {
- lastMethodArg = arg;
- }
-
- WebChannel.id: "myObj"
- }
- QtObject {
- id: myOtherObj
- property var foo: 1
- property var bar: 1
- WebChannel.id: "myOtherObj"
- }
- QtObject {
- id: myFactory
- property var lastObj
- function create(id)
- {
- lastObj = component.createObject(myFactory, {objectName: id});
- return lastObj;
- }
- WebChannel.id: "myFactory"
- }
-
- Component {
- id: component
- QtObject {
- property var myProperty : 0
- function myMethod(arg) {
- mySignal(arg, myProperty);
- }
- signal mySignal(var arg1, var arg2)
- }
- }
-
- TestWebChannel {
- id: webChannel
- transports: [client.serverTransport]
- registeredObjects: [myObj, myOtherObj, myFactory]
- }
-
- function init()
- {
- myObj.myProperty = 1
- client.cleanup();
- }
-
- function test_property()
- {
- var channel = client.createChannel(function(channel) {
- channel.exec({label: "init", value: channel.objects.myObj.myProperty});
- channel.objects.myObj.myPropertyChanged.connect(function() {
- channel.exec({label: "changed", value: channel.objects.myObj.myProperty});
- });
- channel.subscribe("setProperty", function(newValue) {
- channel.objects.myObj.myProperty = newValue;
- });
- });
-
- client.awaitInit();
- var msg = client.awaitMessageSkipIdle();
- compare(msg.data.label, "init");
- compare(msg.data.value, 1);
- compare(myObj.myProperty, 1);
-
- // change property, should be propagated to HTML client and a message be send there
- myObj.myProperty = 2;
- msg = client.awaitMessageSkipIdle();
- compare(msg.data.label, "changed");
- compare(msg.data.value, 2);
- compare(myObj.myProperty, 2);
-
- // now trigger a write from the client side
- webChannel.sendMessage("setProperty", 3);
- msg = client.awaitMessageSkipIdle();
- compare(myObj.myProperty, 3);
-
- // the above write is also propagated to the HTML client
- msg = client.awaitMessageSkipIdle();
- compare(msg.data.label, "changed");
- compare(msg.data.value, 3);
-
- client.awaitIdle();
- }
-
- function test_method()
- {
- var channel = client.createChannel(function (channel) {
- channel.subscribe("invokeMethod", function (arg) {
- channel.objects.myObj.myMethod(arg);
- });
- });
-
- client.awaitInit();
- client.awaitIdle();
-
- webChannel.sendMessage("invokeMethod", "test");
-
- var msg = client.awaitMessage();
- compare(msg.data.type, JSClient.QWebChannelMessageTypes.invokeMethod);
- compare(msg.data.object, "myObj");
- compare(msg.data.args, ["test"]);
-
- compare(lastMethodArg, "test")
- }
-
- function test_signal()
- {
- var channel = client.createChannel(function(channel) {
- channel.objects.myObj.mySignal.connect(function(arg) {
- channel.exec({label: "signalReceived", value: arg});
- });
- });
- client.awaitInit();
-
- var msg = client.awaitMessage();
- compare(msg.data.type, JSClient.QWebChannelMessageTypes.connectToSignal);
- compare(msg.data.object, "myObj");
-
- client.awaitIdle();
-
- myObj.mySignal("test");
-
- msg = client.awaitMessageSkipIdle();
- compare(msg.data.label, "signalReceived");
- compare(msg.data.value, "test");
- }
-
- function test_grouping()
- {
- var channel = client.createChannel(function(channel) {
- channel.subscribe(JSClient.QWebChannelMessageTypes.propertyUpdate, function() {
- channel.exec({label: "gotPropertyUpdate", values: [channel.objects.myObj.myProperty, channel.objects.myOtherObj.foo, channel.objects.myOtherObj.bar]});
- });
- });
- client.awaitInit();
- client.awaitIdle();
-
- // change properties a lot, we expect this to be grouped into a single update notification
- for (var i = 0; i < 10; ++i) {
- myObj.myProperty = i;
- myOtherObj.foo = i;
- myOtherObj.bar = i;
- }
-
- var msg = client.awaitMessage();
- verify(msg);
- compare(msg.data.label, "gotPropertyUpdate");
- compare(msg.data.values, [myObj.myProperty, myOtherObj.foo, myOtherObj.bar]);
-
- client.awaitIdle();
- }
-
- function test_wrapper()
- {
- var channel = client.createChannel(function(channel) {
- channel.objects.myFactory.create("testObj", function(obj) {
- channel.objects["testObj"] = obj;
- obj.mySignal.connect(function(arg1, arg2) {
- channel.exec({label: "signalReceived", args: [arg1, arg2]});
- });
- obj.myProperty = 42;
- obj.myMethod("foobar");
- });
- channel.subscribe("triggerDelete", function() {
- channel.objects.testObj.deleteLater();
- });
- channel.subscribe("report", function() {
- channel.exec({label:"report", obj: channel.objects.testObj})
- });
- });
- client.awaitInit();
-
- var msg = client.awaitMessageSkipIdle();
- compare(msg.data.type, JSClient.QWebChannelMessageTypes.invokeMethod);
- compare(msg.data.object, "myFactory");
- verify(myFactory.lastObj);
- compare(myFactory.lastObj.objectName, "testObj");
-
- msg = client.awaitMessageSkipIdle();
- compare(msg.data.type, JSClient.QWebChannelMessageTypes.connectToSignal);
- verify(msg.data.object);
- var objId = msg.data.object;
-
- msg = client.awaitMessageSkipIdle();
- compare(msg.data.type, JSClient.QWebChannelMessageTypes.connectToSignal);
- compare(msg.data.object, objId);
-
- msg = client.awaitMessageSkipIdle();
- compare(msg.data.type, JSClient.QWebChannelMessageTypes.setProperty);
- compare(msg.data.object, objId);
- compare(myFactory.lastObj.myProperty, 42);
-
- msg = client.awaitMessageSkipIdle();
- compare(msg.data.type, JSClient.QWebChannelMessageTypes.invokeMethod);
- compare(msg.data.object, objId);
- compare(msg.data.args, ["foobar"]);
-
- msg = client.awaitMessageSkipIdle();
- compare(msg.data.label, "signalReceived");
- compare(msg.data.args, ["foobar", 42]);
-
- // pass QObject* on the fly and trigger deleteLater from client side
- webChannel.sendMessage("triggerDelete");
-
- msg = client.awaitMessageSkipIdle();
- compare(msg.data.type, JSClient.QWebChannelMessageTypes.invokeMethod);
- compare(msg.data.object, objId);
-
- client.awaitIdle();
-
- webChannel.sendMessage("report");
-
- msg = client.awaitMessageSkipIdle();
- compare(msg.data.label, "report");
- compare(msg.data.obj, {});
- }
-
- function test_disconnect()
- {
- var channel = client.createChannel(function(channel) {
- channel.objects.myObj.mySignal.connect(function(arg) {
- channel.exec({label: "mySignalReceived", args: [arg]});
- channel.objects.myObj.mySignal.disconnect(this);
- });
- channel.subscribe("report", function() {
- channel.exec({label: "report"});
- });
- });
- client.awaitInit();
-
- var msg = client.awaitMessage();
- compare(msg.data.type, JSClient.QWebChannelMessageTypes.connectToSignal);
- compare(msg.data.object, "myObj");
-
- client.awaitIdle();
-
- myObj.mySignal(42);
-
- msg = client.awaitMessage();
- compare(msg.data.label, "mySignalReceived");
- compare(msg.data.args, [42]);
-
- msg = client.awaitMessage();
- compare(msg.data.type, JSClient.QWebChannelMessageTypes.disconnectFromSignal);
- compare(msg.data.object, "myObj");
-
- myObj.mySignal(0);
-
- // apparently one cannot expect failure in QML, so trigger another message
- // and verify no mySignalReceived was triggered by the above emission
- webChannel.sendMessage("report");
-
- msg = client.awaitMessage();
- compare(msg.data.label, "report");
- }
-}
diff --git a/tests/auto/qml/tst_multiclient.qml b/tests/auto/qml/tst_multiclient.qml
index bca2a03..66357d1 100644
--- a/tests/auto/qml/tst_multiclient.qml
+++ b/tests/auto/qml/tst_multiclient.qml
@@ -56,9 +56,9 @@ TestCase {
id: client2
}
+ property int bar: 0
QtObject {
id: foo
- property int bar: 0
signal ping()
@@ -86,7 +86,7 @@ TestCase {
{
channel.objects.foo.ping.connect(function() {
channel.objects.foo.pong(function(value) {
- channel.exec({pongAnswer: value});
+ channel.pongAnswer = value;
});
});
}
@@ -108,12 +108,7 @@ TestCase {
client1.awaitMessage();
client2.awaitMessage();
- var msg = client1.awaitMessage();
- compare(msg.data.pongAnswer, 1);
- msg = client2.awaitMessage();
- compare(msg.data.pongAnswer, 2);
-
- client1.awaitIdle();
- client2.awaitIdle();
+ compare(c1.pongAnswer, 1);
+ compare(c2.pongAnswer, 2);
}
}
diff --git a/tests/auto/qml/tst_webchannel.qml b/tests/auto/qml/tst_webchannel.qml
index 53c2b38..8ac59eb 100644
--- a/tests/auto/qml/tst_webchannel.qml
+++ b/tests/auto/qml/tst_webchannel.qml
@@ -44,6 +44,7 @@ import QtTest 1.0
import QtWebChannel 1.0
import QtWebChannel.Tests 1.0
+import "qrc:///qtwebchannel/qwebchannel.js" as JSClient
TestCase {
name: "WebChannel"
@@ -52,35 +53,248 @@ TestCase {
id: client
}
+ property var lastMethodArg
+
+ QtObject {
+ id: myObj
+ property int myProperty: 1
+
+ signal mySignal(var arg)
+
+ function myMethod(arg)
+ {
+ lastMethodArg = arg;
+ }
+
+ WebChannel.id: "myObj"
+ }
+ QtObject {
+ id: myOtherObj
+ property var foo: 1
+ property var bar: 1
+ WebChannel.id: "myOtherObj"
+ }
+ QtObject {
+ id: myFactory
+ property var lastObj
+ function create(id)
+ {
+ lastObj = component.createObject(myFactory, {objectName: id});
+ return lastObj;
+ }
+ WebChannel.id: "myFactory"
+ }
+
+ Component {
+ id: component
+ QtObject {
+ property var myProperty : 0
+ function myMethod(arg) {
+ lastMethodArg = arg;
+ }
+ signal mySignal(var arg1, var arg2)
+ }
+ }
+
TestWebChannel {
id: webChannel
transports: [client.serverTransport]
+ registeredObjects: [myObj, myOtherObj, myFactory]
}
- function cleanup()
+ function init()
{
+ myObj.myProperty = 1
client.cleanup();
}
- function test_receiveRawMessage()
+ function test_property()
{
- var channel = client.createChannel(function (channel) {
- channel.send("foobar");
- }, true /* raw */);
- compare(client.awaitRawMessage(), "foobar");
+ compare(myObj.myProperty, 1);
+
+ var initialValue;
+ var changedValue;
+
+ var channel = client.createChannel(function(channel) {
+ initialValue = channel.objects.myObj.myProperty;
+ channel.objects.myObj.myPropertyChanged.connect(function() {
+ changedValue = channel.objects.myObj.myProperty;
+ });
+ // now trigger a write from the client side
+ channel.objects.myObj.myProperty = 3;
+ });
+
+ client.awaitInit();
+ var msg = client.awaitMessage();
+
+ compare(initialValue, 1);
+ compare(myObj.myProperty, 3);
+
+ client.awaitIdle();
+
+ // change property, should be propagated to HTML client and a message be send there
+ myObj.myProperty = 2;
+ compare(myObj.myProperty, 2);
+ client.awaitIdle();
+ compare(changedValue, 2);
}
- function test_sendMessage()
+ function test_method()
{
var channel = client.createChannel(function (channel) {
- channel.subscribe("myMessage", function(payload) {
- channel.send("myMessagePong:" + payload);
+ channel.objects.myObj.myMethod("test");
+ });
+
+ client.awaitInit();
+
+ var msg = client.awaitMessage();
+ compare(msg.type, JSClient.QWebChannelMessageTypes.invokeMethod);
+ compare(msg.object, "myObj");
+ compare(msg.args, ["test"]);
+
+ compare(lastMethodArg, "test")
+
+ client.awaitIdle();
+ }
+
+ function test_signal()
+ {
+ var signalReceivedArg;
+ var channel = client.createChannel(function(channel) {
+ channel.objects.myObj.mySignal.connect(function(arg) {
+ signalReceivedArg = arg;
+ });
+ });
+ client.awaitInit();
+
+ var msg = client.awaitMessage();
+ compare(msg.type, JSClient.QWebChannelMessageTypes.connectToSignal);
+ compare(msg.object, "myObj");
+
+ client.awaitIdle();
+
+ myObj.mySignal("test");
+
+ compare(signalReceivedArg, "test");
+ }
+
+ function test_grouping()
+ {
+ var receivedPropertyUpdates = 0;
+ var properties = 0;
+ var channel = client.createChannel(function(channel) {
+ var originalHandler = channel.handlePropertyUpdate;
+ channel.handlePropertyUpdate = function(message) {
+ originalHandler(message);
+ receivedPropertyUpdates++;
+ properties = [channel.objects.myObj.myProperty, channel.objects.myOtherObj.foo, channel.objects.myOtherObj.bar];
+ };
+ });
+ client.awaitInit();
+ client.awaitIdle();
+
+ // change properties a lot, we expect this to be grouped into a single update notification
+ for (var i = 0; i < 10; ++i) {
+ myObj.myProperty = i;
+ myOtherObj.foo = i;
+ myOtherObj.bar = i;
+ }
+
+ client.awaitIdle();
+ compare(receivedPropertyUpdates, 1);
+ compare(properties, [myObj.myProperty, myOtherObj.foo, myOtherObj.bar]);
+ verify(!client.awaitMessage());
+ }
+
+ function test_wrapper()
+ {
+ var signalArgs;
+ var testObjBeforeDeletion;
+ var testObjAfterDeletion;
+ var channel = client.createChannel(function(channel) {
+ channel.objects.myFactory.create("testObj", function(obj) {
+ channel.objects.testObj = obj;
+ obj.mySignal.connect(function() {
+ signalArgs = arguments;
+ testObjBeforeDeletion = obj;
+ obj.deleteLater();
+ testObjAfterDeletion = obj;
+ });
+ obj.myProperty = 42;
+ obj.myMethod("foobar");
+ });
+ });
+ client.awaitInit();
+
+ var msg = client.awaitMessage();
+ compare(msg.type, JSClient.QWebChannelMessageTypes.invokeMethod);
+ compare(msg.object, "myFactory");
+ verify(myFactory.lastObj);
+ compare(myFactory.lastObj.objectName, "testObj");
+
+ // deleteLater signal connection
+ msg = client.awaitMessage();
+ compare(msg.type, JSClient.QWebChannelMessageTypes.connectToSignal);
+ verify(msg.object);
+ var objId = msg.object;
+
+ // mySignal connection
+ msg = client.awaitMessage();
+ compare(msg.type, JSClient.QWebChannelMessageTypes.connectToSignal);
+ compare(msg.object, objId);
+
+ msg = client.awaitMessage();
+ compare(msg.type, JSClient.QWebChannelMessageTypes.setProperty);
+ compare(msg.object, objId);
+ compare(myFactory.lastObj.myProperty, 42);
+
+ msg = client.awaitMessage();
+ compare(msg.type, JSClient.QWebChannelMessageTypes.invokeMethod);
+ compare(msg.object, objId);
+ compare(msg.args, ["foobar"]);
+ compare(lastMethodArg, "foobar");
+
+ myFactory.lastObj.mySignal("foobar", 42);
+
+ // deleteLater call
+ msg = client.awaitMessage();
+ compare(msg.type, JSClient.QWebChannelMessageTypes.invokeMethod);
+ compare(msg.object, objId);
+
+ compare(signalArgs, {"0": "foobar", "1": 42});
+
+ client.awaitIdle();
+
+ compare(JSON.stringify(testObjBeforeDeletion), JSON.stringify({}));
+ compare(JSON.stringify(testObjAfterDeletion), JSON.stringify({}));
+ compare(JSON.stringify(channel.objects.testObj), JSON.stringify({}));
+ }
+
+ function test_disconnect()
+ {
+ var signalArg;
+ var channel = client.createChannel(function(channel) {
+ channel.objects.myObj.mySignal.connect(function(arg) {
+ signalArg = arg;
+ channel.objects.myObj.mySignal.disconnect(this);
});
- channel.send("initialized");
- }, true /* raw */);
+ });
+ client.awaitInit();
+
+ var msg = client.awaitMessage();
+ compare(msg.type, JSClient.QWebChannelMessageTypes.connectToSignal);
+ compare(msg.object, "myObj");
+
+ client.awaitIdle();
+
+ myObj.mySignal(42);
+ compare(signalArg, 42);
+
+ msg = client.awaitMessage();
+ compare(msg.type, JSClient.QWebChannelMessageTypes.disconnectFromSignal);
+ compare(msg.object, "myObj");
- compare(client.awaitRawMessage(), "initialized");
- webChannel.sendMessage("myMessage", "foobar");
- compare(client.awaitRawMessage(), "myMessagePong:foobar");
+ myObj.mySignal(0);
+ compare(signalArg, 42);
}
}
diff --git a/tests/auto/webchannel/tst_webchannel.cpp b/tests/auto/webchannel/tst_webchannel.cpp
index 632d055..900a6bb 100644
--- a/tests/auto/webchannel/tst_webchannel.cpp
+++ b/tests/auto/webchannel/tst_webchannel.cpp
@@ -227,19 +227,19 @@ void TestWebChannel::testInvokeMethodConversion()
{
int method = metaObject()->indexOfMethod("setInt(int)");
QVERIFY(method != -1);
- QVERIFY(!channel.d_func()->publisher->invokeMethod(this, method, args, QJsonValue()).isEmpty());
+ channel.d_func()->publisher->invokeMethod(this, method, args);
QCOMPARE(m_lastInt, args.at(0).toInt());
}
{
int method = metaObject()->indexOfMethod("setDouble(double)");
QVERIFY(method != -1);
- QVERIFY(!channel.d_func()->publisher->invokeMethod(this, method, args, QJsonValue()).isEmpty());
+ channel.d_func()->publisher->invokeMethod(this, method, args);
QCOMPARE(m_lastDouble, args.at(0).toDouble());
}
{
int method = metaObject()->indexOfMethod("setVariant(QVariant)");
QVERIFY(method != -1);
- QVERIFY(!channel.d_func()->publisher->invokeMethod(this, method, args, QJsonValue()).isEmpty());
+ channel.d_func()->publisher->invokeMethod(this, method, args);
QCOMPARE(m_lastVariant, args.at(0).toVariant());
}
}
diff --git a/tests/auto/webchannel/tst_webchannel.h b/tests/auto/webchannel/tst_webchannel.h
index 4811a5c..bd403dc 100644
--- a/tests/auto/webchannel/tst_webchannel.h
+++ b/tests/auto/webchannel/tst_webchannel.h
@@ -57,7 +57,7 @@ public:
~DummyTransport() {};
public slots:
- void sendTextMessage(const QString &/*message*/) Q_DECL_OVERRIDE
+ void sendMessage(const QJsonObject &/*message*/) Q_DECL_OVERRIDE
{
}
};