diff options
-rw-r--r-- | examples/standalone/websockettransport.cpp | 24 | ||||
-rw-r--r-- | examples/standalone/websockettransport.h | 5 | ||||
-rw-r--r-- | src/webchannel/qmetaobjectpublisher.cpp | 140 | ||||
-rw-r--r-- | src/webchannel/qmetaobjectpublisher_p.h | 33 | ||||
-rw-r--r-- | src/webchannel/qqmlwebchannel.h | 4 | ||||
-rw-r--r-- | src/webchannel/qwebchannel.cpp | 31 | ||||
-rw-r--r-- | src/webchannel/qwebchannel.h | 2 | ||||
-rw-r--r-- | src/webchannel/qwebchannel.js | 218 | ||||
-rw-r--r-- | src/webchannel/qwebchannel_p.h | 2 | ||||
-rw-r--r-- | src/webchannel/qwebchannelabstracttransport.h | 5 | ||||
-rw-r--r-- | tests/auto/qml/Client.qml | 24 | ||||
-rw-r--r-- | tests/auto/qml/testtransport.cpp | 24 | ||||
-rw-r--r-- | tests/auto/qml/testtransport.h | 6 | ||||
-rw-r--r-- | tests/auto/qml/tst_metaobjectpublisher.qml | 320 | ||||
-rw-r--r-- | tests/auto/qml/tst_multiclient.qml | 13 | ||||
-rw-r--r-- | tests/auto/qml/tst_webchannel.qml | 242 | ||||
-rw-r--r-- | tests/auto/webchannel/tst_webchannel.cpp | 6 | ||||
-rw-r--r-- | tests/auto/webchannel/tst_webchannel.h | 2 |
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 { } }; |