diff options
47 files changed, 662 insertions, 1323 deletions
diff --git a/examples/qml/README b/examples/qml/README deleted file mode 100644 index cf0e041..0000000 --- a/examples/qml/README +++ /dev/null @@ -1,3 +0,0 @@ -To run this example, install QtWebKit, QtWebChannel and QtQuickControls modules, then run: - -qmlscene ./example.qml diff --git a/examples/qml/example.qml b/examples/qml/example.qml deleted file mode 100644 index 2d51f27..0000000 --- a/examples/qml/example.qml +++ /dev/null @@ -1,117 +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.1 - -import QtWebChannel 1.0 - -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 - -import QtWebKit 3.0 -import QtWebKit.experimental 1.0 - -ApplicationWindow { - id: window - title: "QtWebChannel Example: QML Server to QtWebKit WebView Client" - width: 600 - height: 400 - - WebChannel { - id: webChannel - - connections: WebViewTransport { - webViewExperimental: webView.experimental - onMessageReceived: { - textEdit.text += "Received message: " + message + "\n"; - } - } - } - - RowLayout { - id: myRow - anchors.fill: parent - ColumnLayout { - id: myCol - Label { - id: caption - text: "QML Server" - font.bold: true - } - TextArea { - Layout.fillHeight: true - Layout.fillWidth: true - id: textEdit - readOnly: true - } - RowLayout { - Label { - id: label - text: "Input: " - } - TextField { - id: input - Layout.fillWidth: true - } - Button { - id: send - text: "Send" - onClicked: { - if (input.text) { - webChannel.sendMessage("message", input.text); - textEdit.text += "Sent message: " + input.text + "\n"; - input.text = "" - } - } - } - } - } - WebView { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumWidth: window.width / 2 - id: webView - url: "index.html" - experimental.preferences.developerExtrasEnabled: true - experimental.preferences.navigatorQtObjectEnabled: true - } - } -} diff --git a/examples/qml/index.html b/examples/qml/index.html deleted file mode 100644 index 8b65dd2..0000000 --- a/examples/qml/index.html +++ /dev/null @@ -1,59 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> - <script type="text/javascript" src="qrc:///qwebchannel/qwebchannel.js"></script> - <script type="text/javascript"> - //BEGIN SETUP - new QWebChannel(navigator.qt, function(channel) { - document.getElementById("send").onclick = function() { - var input = document.getElementById("input"); - var text = input.value; - if (!text) { - return; - } - var output = document.getElementById("output"); - output.innerHTML = output.innerHTML + "Sent message: " + text + "\n"; - input.value = ""; - channel.send(text); - } - - channel.subscribe("message", function(text) { - var output = document.getElementById("output"); - output.innerHTML = output.innerHTML + "Received message: " + text + "\n"; - }); - }, true /* raw web channel */); - //END SETUP - </script> - <style type="text/css"> - * { - padding: 0; - margin: 0; - font-size: 40px; - } - html, body { - height: 100%; - width: 100%; - } - #div { - height: 100%; - padding: 0 10%; - } - #input { - width: 50%; - margin: 0 10px; - } - #output { - width: 100%; - height: 80%; - } - </style> - </head> - <body> - <div id="div"> - <h1>HTML Client</h1> - <textarea id="output" readonly></textarea><br /> - Input: <input id="input" /><input type="submit" id="send" value="Send" onclick="javascript:click();" /> - </div> - </body> -</html> diff --git a/examples/standalone/main.cpp b/examples/standalone/main.cpp index 47f7ea2..05deb24 100644 --- a/examples/standalone/main.cpp +++ b/examples/standalone/main.cpp @@ -40,7 +40,6 @@ ****************************************************************************/ #include "qwebchannel.h" -#include "qwebsockettransport.h" #include <QApplication> #include <QDialog> @@ -49,6 +48,10 @@ #include <QUrl> #include <QDebug> +#include <QtWebSockets/QWebSocketServer> +#include <QtWebSockets/QWebSocket> +#include <QtWebChannel/QWebChannelWebSocketTransport> + #include "ui_dialog.h" class Dialog : public QObject @@ -56,15 +59,18 @@ class Dialog : public QObject Q_OBJECT public: - explicit Dialog(QWebSocketTransport *transport, QObject *parent = 0) + explicit Dialog(const QString &baseUrl, QObject *parent = 0) : QObject(parent) { ui.setupUi(&dialog); dialog.show(); connect(ui.send, SIGNAL(clicked()), SLOT(clicked())); - connect(transport, SIGNAL(baseUrlChanged(QString)), - SLOT(baseUrlChanged(QString))); + + QUrl url = QUrl::fromLocalFile(SOURCE_DIR "/index.html"); + url.setQuery(QStringLiteral("webChannelBaseUrl=") + baseUrl); + ui.output->appendPlainText(tr("Initialization complete, opening browser at %1.").arg(url.toDisplayString())); + QDesktopServices::openUrl(url); } signals: @@ -91,29 +97,55 @@ private slots: ui.input->clear(); } - void baseUrlChanged(const QString &baseUrl) +private: + QDialog dialog; + Ui::Dialog ui; +}; + +// boiler plate code to connect incoming WebSockets to the WebChannel, such that they receive +// messages and can access the published objects. +class TransportHandler : public QObject +{ + Q_OBJECT + +public: + TransportHandler(QWebChannel *channel, QObject *parent = 0) + : QObject(parent) + , m_server(QStringLiteral("QWebChannel Standalone Example Server"), QWebSocketServer::NonSecureMode) + , m_channel(channel) + { + if (!m_server.listen(QHostAddress::LocalHost)) { + qFatal("Failed to open web socket server."); + } + + connect(&m_server, &QWebSocketServer::newConnection, + this, &TransportHandler::handleNewConnection); + } + + QString baseUrl() const { - ui.output->appendPlainText(tr("Initialization complete, opening browser.")); + return m_server.serverUrl().toString(); + } - QUrl url = QUrl::fromLocalFile(SOURCE_DIR "/index.html"); - url.setQuery(QStringLiteral("webChannelBaseUrl=") + baseUrl); - QDesktopServices::openUrl(url); +private slots: + void handleNewConnection() + { + m_channel->connectTo(new QWebChannelWebSocketTransport(m_server.nextPendingConnection())); } private: - QDialog dialog; - Ui::Dialog ui; + QWebSocketServer m_server; + QWebChannel *m_channel; }; int main(int argc, char** argv) { QApplication app(argc, argv); - QWebSocketTransport transport; QWebChannel channel; - channel.connectTo(&transport); + TransportHandler transportHandler(&channel); - Dialog dialog(&transport); + Dialog dialog(transportHandler.baseUrl()); channel.registerObject(QStringLiteral("dialog"), &dialog); diff --git a/examples/standalone/standalone.pro b/examples/standalone/standalone.pro index 7ce0f2b..114be58 100644 --- a/examples/standalone/standalone.pro +++ b/examples/standalone/standalone.pro @@ -1,4 +1,4 @@ -QT += gui webchannel widgets +QT += gui webchannel widgets websockets CONFIG += warn_on diff --git a/src/imports/webchannel/plugin.cpp b/src/imports/webchannel/plugin.cpp index 589e3cb..6507112 100644 --- a/src/imports/webchannel/plugin.cpp +++ b/src/imports/webchannel/plugin.cpp @@ -43,11 +43,6 @@ #include <QtQml/QQmlExtensionPlugin> #include "qmlwebchannel.h" -#include "qwebsockettransport.h" -#include "qmlwebviewtransport.h" -#include "qwebchanneltransportinterface.h" - -QML_DECLARE_INTERFACE_HASMETATYPE(QWebChannelTransportInterface); QT_BEGIN_NAMESPACE @@ -62,13 +57,9 @@ public: void QWebChannelPlugin::registerTypes(const char *uri) { - qmlRegisterInterface<QWebChannelTransportInterface>("QWebChannelTransportInterface"); - int major = 1; int minor = 0; qmlRegisterType<QmlWebChannel>(uri, major, minor, "WebChannel"); - qmlRegisterType<QWebSocketTransport>(uri, major, minor, "WebSocketTransport"); - qmlRegisterType<QmlWebViewTransport>(uri, major, minor, "WebViewTransport"); } QT_END_NAMESPACE diff --git a/src/imports/webchannel/plugins.qmltypes b/src/imports/webchannel/plugins.qmltypes index 6d08f9c..789f2b4 100644 --- a/src/imports/webchannel/plugins.qmltypes +++ b/src/imports/webchannel/plugins.qmltypes @@ -4,95 +4,65 @@ import QtQuick.tooling 1.1 // It is used for QML tooling purposes only. // // This file was auto-generated by: -// 'qmlplugindump -notrelocatable QtWebChannel 5.3' +// 'qmlplugindump -notrelocatable QtWebChannel 1.0' Module { Component { - name: "QMetaObjectPublisher" + name: "QWebChannel" prototype: "QObject" - exports: ["QtWebChannel/MetaObjectPublisher 5.3"] - exportMetaObjectRevisions: [0] - Property { name: "webChannel"; type: "QWebChannel"; isPointer: true } Property { name: "blockUpdates"; type: "bool" } Signal { - name: "webChannelChanged" - Parameter { name: "channel"; type: "QWebChannel"; isPointer: true } - } - Signal { name: "blockUpdatesChanged" Parameter { name: "block"; type: "bool" } } Method { - name: "classInfoForObjects" - type: "QVariantMap" - Parameter { name: "objects"; type: "QVariantMap" } - } - Method { - name: "classInfoForObject" - type: "QVariantMap" - Parameter { name: "object"; type: "QObject"; isPointer: true } + name: "sendMessage" + Parameter { name: "id"; type: "QJsonValue" } + Parameter { name: "data"; type: "QJsonValue" } } Method { - name: "registerObjects" - Parameter { name: "objects"; type: "QVariantMap" } + name: "sendMessage" + Parameter { name: "id"; type: "QJsonValue" } } Method { - name: "handleRequest" - type: "bool" - Parameter { name: "message"; type: "QJsonObject" } + name: "registerObject" + Parameter { name: "id"; type: "string" } + Parameter { name: "object"; type: "QObject"; isPointer: true } } - Method { name: "bench_ensureUpdatesInitialized" } - Method { name: "bench_sendPendingPropertyUpdates" } Method { - name: "bench_registerObjects" - Parameter { name: "objects"; type: "QVariantMap" } + name: "deregisterObject" + Parameter { name: "object"; type: "QObject"; isPointer: true } } - Method { name: "bench_initializeClients" } - Method { name: "test_clientIsIdle"; type: "bool" } } Component { - name: "QWebChannel" - prototype: "QObject" - exports: ["QtWebChannel/WebChannel 5.3"] + name: "QmlWebChannel" + prototype: "QWebChannel" + exports: ["QtWebChannel/WebChannel 1.0"] exportMetaObjectRevisions: [0] - Property { name: "baseUrl"; type: "string"; isReadonly: true } - Property { name: "useSecret"; type: "bool" } - Signal { - name: "baseUrlChanged" - Parameter { name: "baseUrl"; type: "string" } - } - Signal { - name: "rawMessageReceived" - Parameter { name: "rawMessage"; type: "string" } - } - Signal { name: "pongReceived" } - Signal { name: "initialized" } - Signal { - name: "failed" - Parameter { name: "reason"; type: "string" } - } + attachedType: "QmlWebChannelAttached" + Property { name: "transports"; type: "QObject"; isList: true; isReadonly: true } + Property { name: "registeredObjects"; type: "QObject"; isList: true; isReadonly: true } Method { - name: "sendMessage" - Parameter { name: "id"; type: "QJsonValue" } - Parameter { name: "data"; type: "QJsonValue" } - } - Method { - name: "sendMessage" - Parameter { name: "id"; type: "QJsonValue" } + name: "registerObjects" + Parameter { name: "objects"; type: "QVariantMap" } } + Method { name: "test_clientIsIdle"; type: "bool" } Method { - name: "respond" - Parameter { name: "messageId"; type: "QJsonValue" } - Parameter { name: "data"; type: "QJsonValue" } + name: "connectTo" + Parameter { name: "transport"; type: "QObject"; isPointer: true } } Method { - name: "respond" - Parameter { name: "messageId"; type: "QJsonValue" } + name: "disconnectFrom" + Parameter { name: "transport"; type: "QObject"; isPointer: true } } - Method { - name: "sendRawMessage" - Parameter { name: "rawMessage"; type: "string" } + } + Component { + name: "QmlWebChannelAttached" + prototype: "QObject" + Property { name: "id"; type: "string" } + Signal { + name: "idChanged" + Parameter { name: "id"; type: "string" } } - Method { name: "ping" } } } diff --git a/src/imports/webchannel/qmlwebchannel.cpp b/src/imports/webchannel/qmlwebchannel.cpp index dc7398d..e61e9c8 100644 --- a/src/imports/webchannel/qmlwebchannel.cpp +++ b/src/imports/webchannel/qmlwebchannel.cpp @@ -43,6 +43,7 @@ #include "qwebchannel_p.h" #include "qmetaobjectpublisher_p.h" +#include "qwebchannelabstracttransport.h" #include <QtQml/QQmlContext> @@ -100,32 +101,19 @@ QmlWebChannelAttached *QmlWebChannel::qmlAttachedProperties(QObject *obj) void QmlWebChannel::connectTo(QObject *transport) { - if (QWebChannelTransportInterface *iface = qobject_cast<QWebChannelTransportInterface*>(transport)) { - m_connectedObjects.insert(transport, iface); - QWebChannel::connectTo(iface); - connect(transport, SIGNAL(destroyed(QObject*)), SLOT(transportDestroyed(QObject*)), Qt::UniqueConnection); + if (QWebChannelAbstractTransport *realTransport = qobject_cast<QWebChannelAbstractTransport*>(transport)) { + QWebChannel::connectTo(realTransport); } else { - qWarning() << "Cannot connect to transport" << transport << " - it does not implement the QWebChannelTransportInterface."; + qWarning() << "Cannot connect to transport" << transport << " - it is not a QWebChannelAbstractTransport."; } } void QmlWebChannel::disconnectFrom(QObject *transport) { - if (QWebChannelTransportInterface *iface = qobject_cast<QWebChannelTransportInterface*>(transport)) { - QWebChannel::disconnectFrom(iface); - disconnect(transport, SIGNAL(destroyed(QObject*)), this, SLOT(transportDestroyed(QObject*))); - m_connectedObjects.remove(transport); + if (QWebChannelAbstractTransport *realTransport = qobject_cast<QWebChannelAbstractTransport*>(transport)) { + QWebChannel::disconnectFrom(realTransport); } else { - qWarning() << "Cannot disconnect from transport" << transport << " - it does not implement the QWebChannelTransportInterface."; - } -} - -void QmlWebChannel::transportDestroyed(QObject *transport) -{ - QWebChannelTransportInterface *iface = m_connectedObjects.take(transport); - const int idx = d->transports.indexOf(iface); - if (idx != -1) { - d->transports.remove(idx); + qWarning() << "Cannot disconnect from transport" << transport << " - it is not a QWebChannelAbstractTransport."; } } @@ -175,35 +163,36 @@ void QmlWebChannel::registeredObjects_clear(QQmlListProperty<QObject> *prop) return channel->m_registeredObjects.clear(); } -QQmlListProperty<QWebChannelTransportInterface> QmlWebChannel::transports() +QQmlListProperty<QObject> QmlWebChannel::transports() { - return QQmlListProperty<QWebChannelTransportInterface>(this, 0, + return QQmlListProperty<QObject>(this, 0, transports_append, transports_count, transports_at, transports_clear); } -void QmlWebChannel::transports_append(QQmlListProperty<QWebChannelTransportInterface> *prop, QWebChannelTransportInterface *transport) +void QmlWebChannel::transports_append(QQmlListProperty<QObject> *prop, QObject *transport) { - QWebChannel *channel = static_cast<QWebChannel*>(prop->object); + QmlWebChannel *channel = static_cast<QmlWebChannel*>(prop->object); channel->connectTo(transport); } -int QmlWebChannel::transports_count(QQmlListProperty<QWebChannelTransportInterface> *prop) +int QmlWebChannel::transports_count(QQmlListProperty<QObject> *prop) { return static_cast<QmlWebChannel*>(prop->object)->d->transports.size(); } -QWebChannelTransportInterface *QmlWebChannel::transports_at(QQmlListProperty<QWebChannelTransportInterface> *prop, int index) +QObject *QmlWebChannel::transports_at(QQmlListProperty<QObject> *prop, int index) { - return static_cast<QmlWebChannel*>(prop->object)->d->transports.at(index); + QmlWebChannel *channel = static_cast<QmlWebChannel*>(prop->object); + return dynamic_cast<QObject*>(channel->d->transports.at(index)); } -void QmlWebChannel::transports_clear(QQmlListProperty<QWebChannelTransportInterface> *prop) +void QmlWebChannel::transports_clear(QQmlListProperty<QObject> *prop) { QWebChannel *channel = static_cast<QWebChannel*>(prop->object); - foreach (QWebChannelTransportInterface *transport, channel->d->transports) { + foreach (QWebChannelAbstractTransport *transport, channel->d->transports) { channel->disconnectFrom(transport); } Q_ASSERT(channel->d->transports.isEmpty()); diff --git a/src/imports/webchannel/qmlwebchannel.h b/src/imports/webchannel/qmlwebchannel.h index 3610307..6bc7127 100644 --- a/src/imports/webchannel/qmlwebchannel.h +++ b/src/imports/webchannel/qmlwebchannel.h @@ -45,7 +45,6 @@ #include <qwebchannel.h> #include "qmlwebchannelattached.h" -#include "qwebchanneltransportinterface.h" #include <QVector> @@ -58,7 +57,7 @@ class QmlWebChannel : public QWebChannel { Q_OBJECT - Q_PROPERTY( QQmlListProperty<QWebChannelTransportInterface> connections READ transports ); + Q_PROPERTY( QQmlListProperty<QObject> transports READ transports ); Q_PROPERTY( QQmlListProperty<QObject> registeredObjects READ registeredObjects ) public: @@ -68,7 +67,7 @@ public: Q_INVOKABLE void registerObjects(const QVariantMap &objects); QQmlListProperty<QObject> registeredObjects(); - QQmlListProperty<QWebChannelTransportInterface> transports(); + QQmlListProperty<QObject> transports(); // TODO: remove this by replacing QML with C++ tests Q_INVOKABLE bool test_clientIsIdle() const; @@ -80,7 +79,6 @@ public: private Q_SLOTS: void objectIdChanged(const QString &newId); - void transportDestroyed(QObject *transport); private: static void registeredObjects_append(QQmlListProperty<QObject> *prop, QObject *item); @@ -88,14 +86,12 @@ private: static QObject *registeredObjects_at(QQmlListProperty<QObject> *prop, int index); static void registeredObjects_clear(QQmlListProperty<QObject> *prop); - static void transports_append(QQmlListProperty<QWebChannelTransportInterface> *prop, QWebChannelTransportInterface *item); - static int transports_count(QQmlListProperty<QWebChannelTransportInterface> *prop); - static QWebChannelTransportInterface *transports_at(QQmlListProperty<QWebChannelTransportInterface> *prop, int index); - static void transports_clear(QQmlListProperty<QWebChannelTransportInterface> *prop); + static void transports_append(QQmlListProperty<QObject> *prop, QObject *item); + static int transports_count(QQmlListProperty<QObject> *prop); + static QObject *transports_at(QQmlListProperty<QObject> *prop, int index); + static void transports_clear(QQmlListProperty<QObject> *prop); QVector<QObject*> m_registeredObjects; - // required as when the object is destroyed, we must still find the address of the base class somehow - QHash<QObject*, QWebChannelTransportInterface*> m_connectedObjects; }; QML_DECLARE_TYPE( QmlWebChannel ) diff --git a/src/imports/webchannel/qmlwebviewtransport.cpp b/src/imports/webchannel/qmlwebviewtransport.cpp deleted file mode 100644 index 3d4e2ed..0000000 --- a/src/imports/webchannel/qmlwebviewtransport.cpp +++ /dev/null @@ -1,103 +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$ -** -****************************************************************************/ - -#include "qmlwebviewtransport.h" - -#include <QVariantMap> - -QT_USE_NAMESPACE - -QmlWebViewTransport::QmlWebViewTransport(QObject *parent) - : QObject(parent) - , m_webViewExperimental(Q_NULLPTR) - , m_handler(Q_NULLPTR) -{ -} - -QmlWebViewTransport::~QmlWebViewTransport() -{ - -} - -void QmlWebViewTransport::setWebViewExperimental(QObject *webViewExperimental) -{ - if (webViewExperimental != m_webViewExperimental) { - if (m_webViewExperimental) { - disconnect(m_webViewExperimental, 0, this, 0); - } - m_webViewExperimental = webViewExperimental; - connect(m_webViewExperimental, SIGNAL(messageReceived(QVariantMap)), this, SLOT(handleWebViewMessage(QVariantMap))); - emit webViewChanged(webViewExperimental); - } -} - -QObject *QmlWebViewTransport::webViewExperimental() const -{ - return m_webViewExperimental; -} - -void QmlWebViewTransport::sendMessage(const QString &message, int /*clientId*/) const -{ - if (!m_webViewExperimental) { - qWarning("Cannot send message - did you forget to set the webViewExperimental property?"); - return; - } - QMetaObject::invokeMethod(m_webViewExperimental, "postMessage", Q_ARG(QString, message)); -} - -void QmlWebViewTransport::sendMessage(const QByteArray &message, int clientId) const -{ - sendMessage(QString::fromUtf8(message), clientId); -} - -void QmlWebViewTransport::handleWebViewMessage(const QVariantMap &message) -{ - if (m_handler) { - const QString &data = message[QStringLiteral("data")].toString(); - m_handler->handleMessage(data, this, -1); - emit messageReceived(data); - } -} - -void QmlWebViewTransport::setMessageHandler(QWebChannelMessageHandlerInterface *handler) -{ - m_handler = handler; -} diff --git a/src/imports/webchannel/webchannel.pro b/src/imports/webchannel/webchannel.pro index 3042854..c1959f5 100644 --- a/src/imports/webchannel/webchannel.pro +++ b/src/imports/webchannel/webchannel.pro @@ -6,12 +6,10 @@ VPATH += ../../webchannel SOURCES += \ plugin.cpp \ qmlwebchannel.cpp \ - qmlwebchannelattached.cpp \ - qmlwebviewtransport.cpp + qmlwebchannelattached.cpp HEADERS += \ qmlwebchannel.h \ - qmlwebchannelattached.h \ - qmlwebviewtransport.h + qmlwebchannelattached.h load(qml_plugin) diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp index 2914714..096bf7b 100644 --- a/src/webchannel/qmetaobjectpublisher.cpp +++ b/src/webchannel/qmetaobjectpublisher.cpp @@ -42,6 +42,7 @@ #include "qmetaobjectpublisher_p.h" #include "qwebchannel.h" #include "qwebchannel_p.h" +#include "qwebchannelabstracttransport.h" #include <QEvent> #include <QJsonDocument> @@ -394,7 +395,7 @@ QByteArray QMetaObjectPublisher::invokeMethod(QObject *const object, const int m void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments) { - if (!webChannel) { + if (!webChannel || webChannel->d->transports.isEmpty()) { return; } if (!signalToPropertyMap.value(object).contains(signalIndex)) { @@ -544,15 +545,18 @@ QByteArray QMetaObjectPublisher::handleRequest(const QJsonObject &message) return QByteArray(); } -void QMetaObjectPublisher::handleMessage(const QString &message, QWebChannelTransportInterface *transport, int clientId) +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->sendMessage(response, clientId); + transport->sendTextMessage(QString::fromUtf8(response)); } } diff --git a/src/webchannel/qmetaobjectpublisher_p.h b/src/webchannel/qmetaobjectpublisher_p.h index 49b3ed0..dda18ae 100644 --- a/src/webchannel/qmetaobjectpublisher_p.h +++ b/src/webchannel/qmetaobjectpublisher_p.h @@ -51,16 +51,14 @@ #include <QPointer> #include "qwebchannelglobal.h" -#include "qwebchanneltransportinterface.h" QT_BEGIN_NAMESPACE class QWebChannel; - -class Q_WEBCHANNEL_EXPORT QMetaObjectPublisher : public QObject, public QWebChannelMessageHandlerInterface +class Q_WEBCHANNEL_EXPORT QMetaObjectPublisher : public QObject { Q_OBJECT - Q_INTERFACES(QWebChannelMessageHandlerInterface) + public: explicit QMetaObjectPublisher(QWebChannel *webChannel); virtual ~QMetaObjectPublisher(); @@ -161,13 +159,14 @@ public: */ void setBlockUpdates(bool block); +Q_SIGNALS: + void blockUpdatesChanged(bool block); + +public Q_SLOTS: /** * Parse the message as JSON and if it succeeds, call handleRequest with the obtained JSON object. */ - void handleMessage(const QString &message, QWebChannelTransportInterface* transport, int clientId) Q_DECL_OVERRIDE; - -Q_SIGNALS: - void blockUpdatesChanged(bool block); + void handleMessage(const QString &message); protected: void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE; diff --git a/src/webchannel/qwebchannel.cpp b/src/webchannel/qwebchannel.cpp index 75d1a4b..e4cf0d6 100644 --- a/src/webchannel/qwebchannel.cpp +++ b/src/webchannel/qwebchannel.cpp @@ -43,7 +43,7 @@ #include "qwebchannel.h" #include "qwebchannel_p.h" #include "qmetaobjectpublisher_p.h" -#include "qwebchanneltransportinterface.h" +#include "qwebchannelabstracttransport.h" #include <QJsonDocument> #include <QJsonObject> @@ -64,6 +64,14 @@ QByteArray generateJSONMessage(const QJsonValue &id, const QJsonValue &data, boo return doc.toJson(QJsonDocument::Compact); } +void QWebChannelPrivate::_q_transportDestroyed(QObject *object) +{ + const int idx = transports.indexOf(static_cast<QWebChannelAbstractTransport*>(object)); + if (idx != -1) { + transports.remove(idx); + } +} + QWebChannel::QWebChannel(QObject *parent) : QObject(parent) , d(new QWebChannelPrivate) @@ -75,9 +83,6 @@ QWebChannel::QWebChannel(QObject *parent) QWebChannel::~QWebChannel() { - foreach (QWebChannelTransportInterface *transport, d->transports) { - transport->setMessageHandler(Q_NULLPTR); - } } void QWebChannel::registerObjects(const QHash< QString, QObject * > &objects) @@ -109,20 +114,24 @@ void QWebChannel::setBlockUpdates(bool block) d->publisher->setBlockUpdates(block); } -void QWebChannel::connectTo(QWebChannelTransportInterface *transport) +void QWebChannel::connectTo(QWebChannelAbstractTransport *transport) { Q_ASSERT(transport); if (!d->transports.contains(transport)) { d->transports << transport; - transport->setMessageHandler(d->publisher); + connect(transport, &QWebChannelAbstractTransport::textMessageReceived, + d->publisher, &QMetaObjectPublisher::handleMessage, + Qt::UniqueConnection); + connect(transport, SIGNAL(destroyed(QObject*)), + this, SLOT(_q_transportDestroyed(QObject*))); } } -void QWebChannel::disconnectFrom(QWebChannelTransportInterface *transport) +void QWebChannel::disconnectFrom(QWebChannelAbstractTransport *transport) { const int idx = d->transports.indexOf(transport); if (idx != -1) { - transport->setMessageHandler(Q_NULLPTR); + disconnect(transport, 0, this, 0); d->transports.remove(idx); } } @@ -135,9 +144,12 @@ void QWebChannel::sendMessage(const QJsonValue &id, const QJsonValue &data) cons } const QByteArray &message = generateJSONMessage(id, data, false); - foreach (QWebChannelTransportInterface *transport, d->transports) { - transport->sendMessage(message); + 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 76e77b8..5016b52 100644 --- a/src/webchannel/qwebchannel.h +++ b/src/webchannel/qwebchannel.h @@ -51,7 +51,7 @@ QT_BEGIN_NAMESPACE struct QWebChannelPrivate; -class QWebChannelTransportInterface; +class QWebChannelAbstractTransport; class Q_WEBCHANNEL_EXPORT QWebChannel : public QObject { @@ -89,8 +89,8 @@ public: */ void setBlockUpdates(bool block); - void connectTo(QWebChannelTransportInterface *transport); - void disconnectFrom(QWebChannelTransportInterface *transport); + void connectTo(QWebChannelAbstractTransport *transport); + void disconnectFrom(QWebChannelAbstractTransport *transport); Q_SIGNALS: void blockUpdatesChanged(bool block); @@ -100,6 +100,8 @@ public Q_SLOTS: private: QScopedPointer<QWebChannelPrivate> d; + Q_PRIVATE_SLOT(d, void _q_transportDestroyed(QObject*)); + friend class QMetaObjectPublisher; friend class QmlWebChannel; friend class TestWebChannel; }; diff --git a/src/webchannel/qwebchannel.js b/src/webchannel/qwebchannel.js index 278f423..291c10b 100644 --- a/src/webchannel/qwebchannel.js +++ b/src/webchannel/qwebchannel.js @@ -102,10 +102,8 @@ var QWebChannel = function(baseUrlOrSocket, initCallback, rawChannel) this.socket.onmessage = this.messageReceived setTimeout(this.initialized, 0); } else { - ///TODO: use ssl? - var socketUrl = "ws://" + baseUrlOrSocket; ///TODO: use QWebChannel protocol, once custom protcols are supported by QtWebSocket - this.socket = new WebSocket(socketUrl /*, "QWebChannel" */); + this.socket = new WebSocket(baseUrlOrSocket/*, "QWebChannel" */); this.socket.onopen = this.initialized this.socket.onclose = function() @@ -147,7 +145,7 @@ var QWebChannel = function(baseUrlOrSocket, initCallback, rawChannel) channel.send({"id": id, "data": data}); }; - this.objectMap = {}; + this.objects = {}; this.initMetaObjectPublisher = function(doneCallback) { @@ -157,7 +155,7 @@ var QWebChannel = function(baseUrlOrSocket, initCallback, rawChannel) channel.subscribe( QWebChannelMessageTypes.signal, function(payload) { - var object = window[payload.object] || channel.objectMap[payload.object]; + var object = channel.objects[payload.object]; if (object) { object.signalEmitted(payload.signal, payload.args); } else { @@ -171,7 +169,7 @@ var QWebChannel = function(baseUrlOrSocket, initCallback, rawChannel) function(payload) { for (var i in payload) { var data = payload[i]; - var object = window[data.object] || channel.objectMap[data.object]; + var object = channel.objects[data.object]; if (object) { object.propertyUpdate(data.signals, data.properties); } else { @@ -192,7 +190,6 @@ var QWebChannel = function(baseUrlOrSocket, initCallback, rawChannel) for (var objectName in payload) { var data = payload[objectName]; var object = new QObject(objectName, data, channel); - window[objectName] = object; } if (doneCallback) { doneCallback(channel); @@ -213,7 +210,7 @@ var QWebChannel = function(baseUrlOrSocket, initCallback, rawChannel) function QObject(name, data, webChannel) { this.__id__ = name; - webChannel.objectMap[name] = this; + webChannel.objects[name] = this; // List of callbacks that get invoked upon signal emission this.__objectSignals__ = {}; @@ -233,18 +230,23 @@ function QObject(name, data, webChannel) return response; } var objectId = response.id; - if (webChannel.objectMap[objectId]) - return webChannel.objectMap[objectId]; + if (webChannel.objects[objectId]) + return webChannel.objects[objectId]; var qObject = new QObject( objectId, response.data, webChannel ); qObject.destroyed.connect(function() { - if (webChannel.objectMap[objectId] === qObject) { - delete webChannel.objectMap[objectId]; + if (webChannel.objects[objectId] === qObject) { + delete webChannel.objects[objectId]; // reset the now deleted QObject to an empty {} object // just assigning {} though would not have the desired effect, but the // below also ensures all external references will see the empty map - for (var prop in qObject) { - delete qObject[prop]; + // NOTE: this detour is necessary to workaround QTBUG-40021 + var propertyNames = []; + for (var propertyName in qObject) { + propertyNames.push(propertyName); + } + for (var idx in propertyNames) { + delete qObject[propertyNames[idx]]; } } }); diff --git a/src/webchannel/qwebchannel_p.h b/src/webchannel/qwebchannel_p.h index b81640e..fbdf2ef 100644 --- a/src/webchannel/qwebchannel_p.h +++ b/src/webchannel/qwebchannel_p.h @@ -43,22 +43,23 @@ #define QWEBCHANNEL_P_H #include "qwebchannelglobal.h" -#include "qwebchanneltransportinterface.h" #include <QVector> QT_BEGIN_NAMESPACE class QJsonValue; -class QWebChannelTransportInterface; +class QWebChannelAbstractTransport; class QMetaObjectPublisher; Q_WEBCHANNEL_EXPORT QByteArray generateJSONMessage(const QJsonValue &id, const QJsonValue &data, bool response); struct Q_WEBCHANNEL_EXPORT QWebChannelPrivate { - QVector<QWebChannelTransportInterface*> transports; + QVector<QWebChannelAbstractTransport*> transports; QMetaObjectPublisher *publisher; + + void _q_transportDestroyed(QObject* object); }; QT_END_NAMESPACE diff --git a/tests/auto/qml/data/testsetup.js b/src/webchannel/qwebchannelabstracttransport.cpp index c0db83a..86c3121 100644 --- a/tests/auto/qml/data/testsetup.js +++ b/src/webchannel/qwebchannelabstracttransport.cpp @@ -39,9 +39,20 @@ ** ****************************************************************************/ -window.createWebChannel = function(callback, raw) +#include "qwebchannelabstracttransport.h" + +QT_BEGIN_NAMESPACE + +QWebChannelAbstractTransport::QWebChannelAbstractTransport(QObject *parent) +: QObject(parent) { - var baseUrlMatch = /[?&]webChannelBaseUrl=([A-Za-z0-9\-:/\.]+)/.exec(location.search); - var transport = baseUrlMatch ? baseUrlMatch[1] : navigator.qt; - return new QWebChannel(transport, callback, raw); + } + +QWebChannelAbstractTransport::~QWebChannelAbstractTransport() +{ + +} + + +QT_END_NAMESPACE diff --git a/src/webchannel/qwebsockettransport_p.h b/src/webchannel/qwebchannelabstracttransport.h index b9bd9b0..c90f4b1 100644 --- a/src/webchannel/qwebsockettransport_p.h +++ b/src/webchannel/qwebchannelabstracttransport.h @@ -39,51 +39,34 @@ ** ****************************************************************************/ -#ifndef QWEBCHANNELSOCKET_P_H -#define QWEBCHANNELSOCKET_P_H +#ifndef QWEBCHANNELABSTRACTTRANSPORT_H +#define QWEBCHANNELABSTRACTTRANSPORT_H -#include <QtWebSockets/QWebSocketServer> - -#include "qwebchanneltransportinterface.h" +#include <QObject> +#include <qwebchannelglobal.h> QT_BEGIN_NAMESPACE -class QWebSocketTransport; -class QWebSocketTransportPrivate : public QWebSocketServer +class Q_WEBCHANNEL_EXPORT QWebChannelAbstractTransport : public QObject { Q_OBJECT public: - QString m_secret; - QString m_baseUrl; - QWebChannelMessageHandlerInterface *m_messageHandler; - QWebSocketTransport *m_transport; - - bool m_useSecret; - bool m_starting; - - explicit QWebSocketTransportPrivate(QWebSocketTransport* transport, QObject *parent = 0); - virtual ~QWebSocketTransportPrivate(); + explicit QWebChannelAbstractTransport(QObject *parent = 0); + virtual ~QWebChannelAbstractTransport(); - void initLater(); - void sendMessage(const QString &message, int clientId); +public Q_SLOTS: + /** + * Send a text @p message to the remote client. + */ + virtual void sendTextMessage(const QString &message) = 0; Q_SIGNALS: - void failed(const QString &reason); - void initialized(); - void baseUrlChanged(const QString &baseUrl); - void textDataReceived(const QString &message); - -private Q_SLOTS: - void validateNewConnection(); - void init(); - void socketError(); - void messageReceived(const QString &message); - void clientDisconnected(); - -private: - QVector<QWebSocket*> m_clients; + /** + * Emitted when a new text message was received from the remote client. + */ + void textMessageReceived(const QString &message); }; QT_END_NAMESPACE -#endif // QWEBCHANNELSOCKET_P_H +#endif // QWEBCHANNELABSTRACTTRANSPORT_H diff --git a/src/webchannel/qwebchanneltransportinterface.h b/src/webchannel/qwebchanneltransportinterface.h deleted file mode 100644 index c532732..0000000 --- a/src/webchannel/qwebchanneltransportinterface.h +++ /dev/null @@ -1,94 +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$ -** -****************************************************************************/ - -#ifndef QWEBCHANNELTRANSPORTINTERFACE_H -#define QWEBCHANNELTRANSPORTINTERFACE_H - -#include <QObject> - -#include <QtWebChannel/qwebchannelglobal.h> - -QT_BEGIN_NAMESPACE - -class QWebChannelTransportInterface; -class Q_WEBCHANNEL_EXPORT QWebChannelMessageHandlerInterface -{ -public: - virtual ~QWebChannelMessageHandlerInterface() {} - - /** - * Handle a text message from a web channel client. - */ - virtual void handleMessage(const QString &message, QWebChannelTransportInterface* transport, int clientId) = 0; -}; - -#define QWebChannelMessageHandlerInterface_iid "org.qt-project.Qt.QWebChannelMessageHandlerInterface" -Q_DECLARE_INTERFACE(QWebChannelMessageHandlerInterface, QWebChannelMessageHandlerInterface_iid); -Q_DECLARE_METATYPE(QWebChannelMessageHandlerInterface*) - -class Q_WEBCHANNEL_EXPORT QWebChannelTransportInterface -{ -public: - virtual ~QWebChannelTransportInterface() {} - - /** - * Send a text message to all web channel clients. - */ - virtual void sendMessage(const QString &message, int clientId = -1) const = 0; - - /** - * Send a binary message to all web channel clients. - */ - virtual void sendMessage(const QByteArray &message, int clientId = -1) const = 0; - - /** - * Sets the message handler that will be called on incoming messages from web channel clients. - */ - virtual void setMessageHandler(QWebChannelMessageHandlerInterface *handler) = 0; -}; - -#define QWebChannelTransportInterface_iid "org.qt-project.Qt.QWebChannelTransportInterface" -Q_DECLARE_INTERFACE(QWebChannelTransportInterface, QWebChannelTransportInterface_iid); -Q_DECLARE_METATYPE(QWebChannelTransportInterface*) - -QT_END_NAMESPACE - -#endif // QWEBCHANNELTRANSPORTINTERFACE_H diff --git a/tests/auto/qml/TestWebView.qml b/src/webchannel/qwebchannelwebsockettransport.cpp index 3b12f38..59c9538 100644 --- a/tests/auto/qml/TestWebView.qml +++ b/src/webchannel/qwebchannelwebsockettransport.cpp @@ -39,43 +39,41 @@ ** ****************************************************************************/ -import QtQuick 2.0 -import QtTest 1.0 +#include "qwebchannelwebsockettransport.h" -import QtWebKit 3.0 -import QtWebKit.experimental 1.0 +/*! + \inmodule QtWebChannel + \brief QWebChannelAbstractSocket implementation that uses a QWebSocket internally. -WebView { - id: view - property var lastLoadStatus + The transport delegates all messages received over the QWebSocket over its + textMessageReceived signal. Analogously, all calls to sendTextMessage will + be send over the QWebSocket to the remote client. +*/ - experimental.preferences.developerExtrasEnabled: true - experimental.preferences.navigatorQtObjectEnabled: true +QT_BEGIN_NAMESPACE - onLoadingChanged: { - // NOTE: we cannot use spy.signalArguments nor save the loadRequest anywhere, as it gets - // deleted after the slots connected to the signal have finished... i.e. it's a weak pointer, - // not a shared pointer. As such, we have to copy out the interesting data we need later on here... - lastLoadStatus = loadRequest.status - } +struct QWebChannelWebSocketTransportPrivate +{ + QWebSocket *socket; +}; - SignalSpy { - id: loadingSpy - target: view - signalName: "onLoadingChanged" - } +QWebChannelWebSocketTransport::QWebChannelWebSocketTransport(QWebSocket *socket) +: QWebChannelAbstractTransport(socket) +, d(new QWebChannelWebSocketTransportPrivate) +{ + d->socket = socket; + connect(socket, &QWebSocket::textMessageReceived, + this, &QWebChannelWebSocketTransport::textMessageReceived); +} + +QWebChannelWebSocketTransport::~QWebChannelWebSocketTransport() +{ - function waitForLoaded() - { - do { - loadingSpy.wait(500); - } while (loading); - return lastLoadStatus == WebView.LoadSucceededStatus; - } +} - function clear() - { - url = ""; - loadingSpy.clear() - } +void QWebChannelWebSocketTransport::sendTextMessage(const QString &message) +{ + d->socket->sendTextMessage(message); } + +QT_END_NAMESPACE diff --git a/src/webchannel/qwebchannelwebsockettransport.h b/src/webchannel/qwebchannelwebsockettransport.h new file mode 100644 index 0000000..b718b9b --- /dev/null +++ b/src/webchannel/qwebchannelwebsockettransport.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QWEBCHANNELWEBSOCKETTRANSPORT_H +#define QWEBCHANNELWEBSOCKETTRANSPORT_H + +#include <QObject> +#include <QtWebChannel/QWebChannelAbstractTransport> +#include <QtWebChannel/qwebchannelglobal.h> +#include <QtWebSockets/QWebSocket> + +QT_BEGIN_NAMESPACE + +struct QWebChannelWebSocketTransportPrivate; +class Q_WEBCHANNEL_EXPORT QWebChannelWebSocketTransport : public QWebChannelAbstractTransport +{ + Q_OBJECT +public: + explicit QWebChannelWebSocketTransport(QWebSocket *socket); + virtual ~QWebChannelWebSocketTransport(); + + void sendTextMessage(const QString &message) Q_DECL_OVERRIDE; + +private: + QScopedPointer<QWebChannelWebSocketTransportPrivate> d; +}; + +QT_END_NAMESPACE + +#endif // QWEBCHANNELWEBSOCKETTRANSPORT_H diff --git a/src/webchannel/qwebsockettransport.cpp b/src/webchannel/qwebsockettransport.cpp deleted file mode 100644 index 8c25a9b..0000000 --- a/src/webchannel/qwebsockettransport.cpp +++ /dev/null @@ -1,212 +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$ -** -****************************************************************************/ - -#include "qwebsockettransport.h" -#include "qwebsockettransport_p.h" - -#include <QUuid> - -#include <QtWebSockets/QWebSocket> - -QT_USE_NAMESPACE - -//BEGIN QWebSocketTransportPrivate - -QWebSocketTransportPrivate::QWebSocketTransportPrivate(QWebSocketTransport *transport, QObject *parent) - : QWebSocketServer(QStringLiteral("QWebChannel Server"), NonSecureMode, parent) - , m_messageHandler(Q_NULLPTR) - , m_transport(transport) - , m_useSecret(true) - , m_starting(false) -{ - connect(this, SIGNAL(acceptError(QAbstractSocket::SocketError)), - SLOT(socketError())); - connect(this, SIGNAL(newConnection()), - SLOT(validateNewConnection())); -} - -QWebSocketTransportPrivate::~QWebSocketTransportPrivate() -{ - close(); - qDeleteAll(m_clients); -} - -void QWebSocketTransportPrivate::initLater() -{ - if (m_starting) - return; - metaObject()->invokeMethod(this, "init", Qt::QueuedConnection); - m_starting = true; -} - -void QWebSocketTransportPrivate::sendMessage(const QString &message, int clientId) -{ - if (clientId == -1) { - foreach (QWebSocket *client, m_clients) { - client->sendTextMessage(message); - } - } else { - m_clients.at(clientId)->sendTextMessage(message); - } -} - -void QWebSocketTransportPrivate::validateNewConnection() -{ - QWebSocket *client = nextPendingConnection(); - // FIXME: client->protocol() != QStringLiteral("QWebChannel") - // protocols are not supported in QtWebSockets yet... - if (m_useSecret && client->requestUrl().path() != m_secret) - { - client->close(QWebSocketProtocol::CloseCodeBadOperation); - client->deleteLater(); - } else { - connect(client, SIGNAL(textMessageReceived(QString)), - SLOT(messageReceived(QString))); - connect(client, SIGNAL(disconnected()), - SLOT(clientDisconnected())); - m_clients << client; - } -} - -void QWebSocketTransportPrivate::init() -{ - close(); - - m_starting = false; - if (m_useSecret) { - m_secret = QUuid::createUuid().toString(); - // replace { by / - m_secret[0] = QLatin1Char('/'); - // chop of trailing } - m_secret.chop(1); - } - - if (!listen(QHostAddress::LocalHost)) { - emit failed(errorString()); - return; - } - - m_baseUrl = QStringLiteral("127.0.0.1:%1%2").arg(serverPort()).arg(m_secret); - emit initialized(); - emit baseUrlChanged(m_baseUrl); -} - -void QWebSocketTransportPrivate::socketError() -{ - emit failed(errorString()); -} - -void QWebSocketTransportPrivate::messageReceived(const QString &message) -{ - if (m_messageHandler) { - QWebSocket *client = qobject_cast<QWebSocket*>(sender()); - m_messageHandler->handleMessage(message, m_transport, m_clients.indexOf(client)); - } - emit textDataReceived(message); -} - -void QWebSocketTransportPrivate::clientDisconnected() -{ - QWebSocket *client = qobject_cast<QWebSocket*>(sender()); - if (!client) { - return; - } - const int idx = m_clients.indexOf(client); - Q_ASSERT(idx != -1); - m_clients.remove(idx); - client->deleteLater(); -} - -//END QWebSocketTransportPrivate - -QWebSocketTransport::QWebSocketTransport(QObject *parent) - : QObject(parent) - , d(new QWebSocketTransportPrivate(this)) -{ - connect(d.data(), SIGNAL(textDataReceived(QString)), - SIGNAL(messageReceived(QString))); - connect(d.data(), SIGNAL(failed(QString)), - SIGNAL(failed(QString))); - connect(d.data(), SIGNAL(initialized()), - SIGNAL(initialized())); - connect(d.data(), SIGNAL(baseUrlChanged(QString)), - SIGNAL(baseUrlChanged(QString))); - - d->initLater(); -} - -QWebSocketTransport::~QWebSocketTransport() -{ - -} - -void QWebSocketTransport::sendMessage(const QByteArray &message, int clientId) const -{ - d->sendMessage(QString::fromUtf8(message), clientId); -} - -void QWebSocketTransport::sendMessage(const QString &message, int clientId) const -{ - d->sendMessage(message, clientId); -} - -void QWebSocketTransport::setMessageHandler(QWebChannelMessageHandlerInterface *handler) -{ - d->m_messageHandler = handler; -} - -QString QWebSocketTransport::baseUrl() const -{ - return d->m_baseUrl; -} - -void QWebSocketTransport::setUseSecret(bool s) -{ - if (d->m_useSecret == s) - return; - d->m_useSecret = s; - d->initLater(); -} - -bool QWebSocketTransport::useSecret() const -{ - return d->m_useSecret; -} diff --git a/src/webchannel/qwebsockettransport.h b/src/webchannel/qwebsockettransport.h deleted file mode 100644 index b3e484f..0000000 --- a/src/webchannel/qwebsockettransport.h +++ /dev/null @@ -1,81 +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$ -** -****************************************************************************/ - -#ifndef QWEBSOCKETTRANSPORT_H -#define QWEBSOCKETTRANSPORT_H - -#include "qwebchanneltransportinterface.h" - -QT_BEGIN_NAMESPACE - -class QWebSocketTransportPrivate; -class Q_WEBCHANNEL_EXPORT QWebSocketTransport : public QObject, public QWebChannelTransportInterface -{ - Q_OBJECT - Q_INTERFACES(QWebChannelTransportInterface) - Q_PROPERTY(QString baseUrl READ baseUrl NOTIFY baseUrlChanged) - Q_PROPERTY(bool useSecret READ useSecret WRITE setUseSecret) -public: - explicit QWebSocketTransport(QObject *parent = 0); - ~QWebSocketTransport() Q_DECL_OVERRIDE; - - void sendMessage(const QByteArray &message, int clientId = -1) const Q_DECL_OVERRIDE; - void sendMessage(const QString &message, int clientId = -1) const Q_DECL_OVERRIDE; - void setMessageHandler(QWebChannelMessageHandlerInterface *handler) Q_DECL_OVERRIDE; - - QString baseUrl() const; - - void setUseSecret(bool); - bool useSecret() const; - -Q_SIGNALS: - void baseUrlChanged(const QString &baseUrl); - void failed(const QString &reason); - void initialized(); - void messageReceived(const QString &message); - -private: - QScopedPointer<QWebSocketTransportPrivate> d; -}; - -QT_END_NAMESPACE - -#endif // QWEBSOCKETTRANSPORT_H diff --git a/src/webchannel/webchannel.pro b/src/webchannel/webchannel.pro index c6e6d69..a50beaa 100644 --- a/src/webchannel/webchannel.pro +++ b/src/webchannel/webchannel.pro @@ -12,20 +12,20 @@ OTHER_FILES = \ PUBLIC_HEADERS += \ qwebchannel.h \ - qwebchanneltransport.h \ - qwebsockettransport.h + qwebchannelabstracttransport.h \ + qwebchannelwebsockettransport.h PRIVATE_HEADERS += \ qwebchannel_p.h \ qmetaobjectpublisher_p.h \ - qwebsockettransport_p.h \ variantargument_p.h \ signalhandler_p.h SOURCES += \ qwebchannel.cpp \ qmetaobjectpublisher.cpp \ - qwebsockettransport.cpp + qwebchannelabstracttransport.cpp \ + qwebchannelwebsockettransport.cpp qtHaveModule(qml) { QT += qml diff --git a/sync.profile b/sync.profile index 5d7cd6b..f0e6c33 100644 --- a/sync.profile +++ b/sync.profile @@ -15,7 +15,4 @@ "qtwebsockets" => "refs/heads/dev", # optional dependencies: "qtdeclarative" => "refs/heads/dev", - # TODO: disabled for now as it breaks CI builds on OSX - # requires changes to qtqa scripts as discussed with sifalt, sahumada, tronical - # "qtwebkit" => "", ); diff --git a/tests/auto/qml/WebChannelTest.qml b/tests/auto/qml/Client.qml index 9a0baa9..609fbac 100644 --- a/tests/auto/qml/WebChannelTest.qml +++ b/tests/auto/qml/Client.qml @@ -43,77 +43,86 @@ import QtQuick 2.0 import QtTest 1.0 import QtWebChannel 1.0 +import QtWebChannel.Tests 1.0 +import "qrc:///qwebchannel/qwebchannel.js" as Client -TestCase { - property var lastLoadStatus - property bool useWebViewTransport: false +Item { + TestTransport { + id: serverTransport + } + readonly property var serverTransport: serverTransport - // only run after the webchannel has finished initialization - when: webSocketTransport.baseUrl != "" + property var clientMessages: [] - WebViewTransport { - id: webViewTransport - webViewExperimental: defaultView.experimental - } - WebSocketTransport { - id: webSocketTransport - } + property bool debug: false - TestWebView { - id: defaultView - } + QtObject { + id: clientTransport + + property var send; + property var onmessage; - WebChannel { - id: webChannel + function postMessage(message) + { + if (debug) { + console.log("client posts message: ", message); + } + clientMessages.push(message); + serverTransport.textMessageReceived(message); + } + + Component.onCompleted: { + serverTransport.sendTextMessageRequested.connect(function(message) { + if (debug) { + console.log("client received message: ", message); + } + onmessage({data:message}); + }); + } } - property var webChannel: webChannel + readonly property var clientTransport: clientTransport + + Timer { + id: timer + running: false + repeat: false - SignalSpy { - id: rawMessageSpy - target: useWebViewTransport ? webViewTransport : webSocketTransport; - signalName: "onMessageReceived" + property var callback + + onTriggered: { + callback(); + } } - property var rawMessageSpy: rawMessageSpy - property var rawMessageIdx: 0; - function urlForFile(file) + function setTimeout(callback, delay) { - verify(useWebViewTransport || webSocketTransport.baseUrl != "", "webSocketTransport.baseUrl is empty"); - return "data/" + file + (!useWebViewTransport ? "?webChannelBaseUrl=" + webSocketTransport.baseUrl : ""); + if (timer.running) { + console.error("nested calls to setTimeout are not supported!", JSON.stringify(callback), JSON.stringify(timer.callback)); + return; + } + timer.callback = callback; + // note: an interval of 0 is directly triggered, so add a little padding + timer.interval = delay + 1; + timer.running = true; } - // load file in the given view or use the global one by default - function loadUrl(file, view) + function createChannel(callback, raw) { - if (useWebViewTransport) { - webChannel.disconnectFrom(webSocketTransport); - webChannel.connectTo(webViewTransport); - } else { - webChannel.disconnectFrom(webViewTransport); - webChannel.connectTo(webSocketTransport); - } - if (!view) { - view = defaultView; - } - view.url = urlForFile(file); - view.waitForLoaded(); + return new Client.QWebChannel(clientTransport, callback, raw); } function cleanup() { - defaultView.clear(); - rawMessageSpy.clear(); - rawMessageIdx = 0; + clientMessages = []; + timer.running = false; } function awaitRawMessage() { - rawMessageSpy.wait(500); - if (rawMessageSpy.signalArguments.length <= rawMessageIdx) { - // still no message received, fail - return null; + for (var i = 0; i < 10 && !clientMessages.length; ++i) { + wait(10); } - return rawMessageSpy.signalArguments[rawMessageIdx++][0]; + return clientMessages.shift(); } function awaitMessage() @@ -131,7 +140,7 @@ TestCase { verify(msg); verify(msg.data); verify(msg.data.type); - compare(msg.data.type, qWebChannelMessageTypes.init); + compare(msg.data.type, Client.QWebChannelMessageTypes.init); } function awaitIdle() @@ -139,19 +148,19 @@ TestCase { var msg = awaitMessage(); verify(msg); verify(msg.data); - compare(msg.data.type, qWebChannelMessageTypes.idle); + compare(msg.data.type, Client.QWebChannelMessageTypes.idle); verify(webChannel.test_clientIsIdle()) } - property var qWebChannelMessageTypes: ({ - signal: 1, - propertyUpdate: 2, - init: 3, - idle: 4, - debug: 5, - invokeMethod: 6, - connectToSignal: 7, - disconnectFromSignal: 8, - setProperty: 9, - }); + function awaitMessageSkipIdle() + { + var msg; + do { + msg = awaitMessage(); + verify(msg); + verify(msg.data); + } while (msg.data.type === Client.QWebChannelMessageTypes.idle); + return msg; + } + } diff --git a/tests/auto/qml/data/bench_init.html b/tests/auto/qml/data/bench_init.html deleted file mode 100644 index 7d3af5b..0000000 --- a/tests/auto/qml/data/bench_init.html +++ /dev/null @@ -1,13 +0,0 @@ -<html> - <head> - <script type="text/javascript" src="qrc:///qwebchannel/qwebchannel.js"></script> - <script type="text/javascript" src="testsetup.js"></script> - <script type="text/javascript"> - //BEGIN SETUP - createWebChannel(function(channel) {}); - //END SETUP - </script> - </head> - <body> - </body> -</html> diff --git a/tests/auto/qml/data/disconnect.html b/tests/auto/qml/data/disconnect.html deleted file mode 100644 index a9a479c..0000000 --- a/tests/auto/qml/data/disconnect.html +++ /dev/null @@ -1,21 +0,0 @@ -<html> - <head> - <script type="text/javascript" src="qrc:///qwebchannel/qwebchannel.js"></script> - <script type="text/javascript" src="testsetup.js"></script> - <script type="text/javascript"> - //BEGIN SETUP - createWebChannel(function(channel) { - myObj.mySignal.connect(function(arg) { - channel.exec({label: "mySignalReceived", args: [arg]}); - myObj.mySignal.disconnect(this); - }); - channel.subscribe("report", function() { - channel.exec({label: "report"}); - }); - }); - //END SETUP - </script> - </head> - <body> - </body> -</html> diff --git a/tests/auto/qml/data/grouping.html b/tests/auto/qml/data/grouping.html deleted file mode 100644 index f6bc33c..0000000 --- a/tests/auto/qml/data/grouping.html +++ /dev/null @@ -1,17 +0,0 @@ -<html> - <head> - <script type="text/javascript" src="qrc:///qwebchannel/qwebchannel.js"></script> - <script type="text/javascript" src="testsetup.js"></script> - <script type="text/javascript"> - //BEGIN SETUP - window.channel = createWebChannel(function(channel) { - channel.subscribe(QWebChannelMessageTypes.propertyUpdate, function() { - channel.exec({label: "gotPropertyUpdate", values: [myObj.myProperty, myOtherObj.foo, myOtherObj.bar]}); - }); - }); - //END SETUP - </script> - </head> - <body> - </body> -</html> diff --git a/tests/auto/qml/data/method.html b/tests/auto/qml/data/method.html deleted file mode 100644 index 6dbaa90..0000000 --- a/tests/auto/qml/data/method.html +++ /dev/null @@ -1,17 +0,0 @@ -<html> - <head> - <script type="text/javascript" src="qrc:///qwebchannel/qwebchannel.js"></script> - <script type="text/javascript" src="testsetup.js"></script> - <script type="text/javascript"> - //BEGIN SETUP - createWebChannel(function(channel) { - channel.subscribe("invokeMethod", function(arg) { - myObj.myMethod(arg); - }); - }); - //END SETUP - </script> - </head> - <body> - </body> -</html> diff --git a/tests/auto/qml/data/multiclient.html b/tests/auto/qml/data/multiclient.html deleted file mode 100644 index 1573a1a..0000000 --- a/tests/auto/qml/data/multiclient.html +++ /dev/null @@ -1,19 +0,0 @@ -<html> - <head> - <script type="text/javascript" src="qrc:///qwebchannel/qwebchannel.js"></script> - <script type="text/javascript" src="testsetup.js"></script> - <script type="text/javascript"> - //BEGIN SETUP - createWebChannel(function(channel) { - foo.ping.connect(function() { - foo.pong(function(value) { - channel.exec({pongAnswer: value}); - }); - }); - }); - //END SETUP - </script> - </head> - <body> - </body> -</html> diff --git a/tests/auto/qml/data/property.html b/tests/auto/qml/data/property.html deleted file mode 100644 index 49b3811..0000000 --- a/tests/auto/qml/data/property.html +++ /dev/null @@ -1,21 +0,0 @@ -<html> - <head> - <script type="text/javascript" src="qrc:///qwebchannel/qwebchannel.js"></script> - <script type="text/javascript" src="testsetup.js"></script> - <script type="text/javascript"> - //BEGIN SETUP - createWebChannel(function(channel) { - channel.exec({label: "init", value: myObj.myProperty}); - myObj.myPropertyChanged.connect(function() { - channel.exec({label: "changed", value: myObj.myProperty}); - }); - channel.subscribe("setProperty", function(newValue) { - myObj.myProperty = newValue; - }); - }); - //END SETUP - </script> - </head> - <body> - </body> -</html> diff --git a/tests/auto/qml/data/receiveRaw.html b/tests/auto/qml/data/receiveRaw.html deleted file mode 100644 index 139b2b1..0000000 --- a/tests/auto/qml/data/receiveRaw.html +++ /dev/null @@ -1,15 +0,0 @@ -<html> - <head> - <script type="text/javascript" src="qrc:///qwebchannel/qwebchannel.js"></script> - <script type="text/javascript" src="testsetup.js"></script> - <script type="text/javascript"> - //BEGIN SETUP - createWebChannel(function(channel) { - channel.send("foobar"); - }, true /* raw */); - //END SETUP - </script> - </head> - <body> - </body> -</html> diff --git a/tests/auto/qml/data/send.html b/tests/auto/qml/data/send.html deleted file mode 100644 index c60fbf4..0000000 --- a/tests/auto/qml/data/send.html +++ /dev/null @@ -1,17 +0,0 @@ -<html> - <head> - <script type="text/javascript" src="qrc:///qwebchannel/qwebchannel.js"></script> - <script type="text/javascript" src="testsetup.js"></script> - <script type="text/javascript"> - //BEGIN SETUP - createWebChannel(function(channel) { - channel.subscribe("myMessage", function(payload) { - channel.send("myMessagePong:" + payload); - }); - }, true /* raw */); - //END SETUP - </script> - </head> - <body> - </body> -</html> diff --git a/tests/auto/qml/data/signal.html b/tests/auto/qml/data/signal.html deleted file mode 100644 index bdce0c7..0000000 --- a/tests/auto/qml/data/signal.html +++ /dev/null @@ -1,17 +0,0 @@ -<html> - <head> - <script type="text/javascript" src="qrc:///qwebchannel/qwebchannel.js"></script> - <script type="text/javascript" src="testsetup.js"></script> - <script type="text/javascript"> - //BEGIN SETUP - createWebChannel(function(channel) { - myObj.mySignal.connect(function(arg) { - channel.exec({label: "signalReceived", value: arg}); - }); - }); - //END SETUP - </script> - </head> - <body> - </body> -</html> diff --git a/tests/auto/qml/data/wrapper.html b/tests/auto/qml/data/wrapper.html deleted file mode 100644 index df368ee..0000000 --- a/tests/auto/qml/data/wrapper.html +++ /dev/null @@ -1,28 +0,0 @@ -<html> - <head> - <script type="text/javascript" src="qrc:///qwebchannel/qwebchannel.js"></script> - <script type="text/javascript" src="testsetup.js"></script> - <script type="text/javascript"> - //BEGIN SETUP - createWebChannel(function(channel) { - myFactory.create("testObj", function(obj) { - window[obj.objectName] = obj; - obj.mySignal.connect(function(arg1, arg2) { - channel.exec({label: "signalReceived", args: [arg1, arg2]}); - }); - obj.myProperty = 42; - obj.myMethod("foobar"); - }); - channel.subscribe("triggerDelete", function() { - testObj.deleteLater(); - }); - channel.subscribe("report", function() { - channel.exec({label:"report", obj: testObj}) - }); - }); - //END SETUP - </script> - </head> - <body> - </body> -</html> diff --git a/tests/auto/qml/qml.cpp b/tests/auto/qml/qml.cpp index 7267842..0612bdb 100644 --- a/tests/auto/qml/qml.cpp +++ b/tests/auto/qml/qml.cpp @@ -40,5 +40,17 @@ ****************************************************************************/ #include <QtQuickTest/quicktest.h> +#include <QtQml/qqml.h> -QUICK_TEST_MAIN(qml) +#ifndef QUICK_TEST_SOURCE_DIR +#define QUICK_TEST_SOURCE_DIR Q_NULLPTR +#endif + +#include "testtransport.h" + +int main(int argc, char **argv) +{ + qmlRegisterType<TestTransport>("QtWebChannel.Tests", 1, 0, "TestTransport"); + + return quick_test_main(argc, argv, "qml", QUICK_TEST_SOURCE_DIR); +} diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro index f86dc90..ddef2cb 100644 --- a/tests/auto/qml/qml.pro +++ b/tests/auto/qml/qml.pro @@ -8,7 +8,11 @@ CONFIG += warn_on qmltestcase IMPORTPATH += $$OUT_PWD/../../../qml $$PWD SOURCES += \ - qml.cpp + qml.cpp \ + testtransport.cpp + +HEADERS += \ + testtransport.h OTHER_FILES += \ WebChannelTest.qml \ diff --git a/tests/auto/qml/testtransport.cpp b/tests/auto/qml/testtransport.cpp new file mode 100644 index 0000000..a332955 --- /dev/null +++ b/tests/auto/qml/testtransport.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "testtransport.h" + +QT_BEGIN_NAMESPACE + +TestTransport::TestTransport(QObject *parent) +: QWebChannelAbstractTransport(parent) +{ + +} + +void TestTransport::sendTextMessage(const QString &message) +{ + emit sendTextMessageRequested(message); +} + +QT_END_NAMESPACE diff --git a/src/imports/webchannel/qmlwebviewtransport.h b/tests/auto/qml/testtransport.h index 8fbfdd9..dd07832 100644 --- a/src/imports/webchannel/qmlwebviewtransport.h +++ b/tests/auto/qml/testtransport.h @@ -39,41 +39,25 @@ ** ****************************************************************************/ -#ifndef QMLWEBVIEWTRANSPORT_H -#define QMLWEBVIEWTRANSPORT_H +#ifndef TESTTRANSPORT_H +#define TESTTRANSPORT_H -#include <qwebchanneltransportinterface.h> +#include <QtWebChannel/QWebChannelAbstractTransport> QT_BEGIN_NAMESPACE -class QmlWebViewTransport : public QObject, public QWebChannelTransportInterface +class TestTransport : public QWebChannelAbstractTransport { Q_OBJECT - Q_INTERFACES(QWebChannelTransportInterface) - Q_PROPERTY(QObject *webViewExperimental READ webViewExperimental WRITE setWebViewExperimental NOTIFY webViewChanged) public: - explicit QmlWebViewTransport(QObject *parent = 0); - ~QmlWebViewTransport() Q_DECL_OVERRIDE; + explicit TestTransport(QObject *parent = 0); - void sendMessage(const QString &message, int clientId) const Q_DECL_OVERRIDE; - void sendMessage(const QByteArray &message, int clientId) const Q_DECL_OVERRIDE; - void setMessageHandler(QWebChannelMessageHandlerInterface *handler) Q_DECL_OVERRIDE; - - void setWebViewExperimental(QObject *webViewExperimental); - QObject *webViewExperimental() const; + virtual void sendTextMessage(const QString &message); Q_SIGNALS: - void webViewChanged(QObject *webViewExperimental); - void messageReceived(const QString &message); - -private Q_SLOTS: - void handleWebViewMessage(const QVariantMap &message); - -private: - QObject *m_webViewExperimental; - QWebChannelMessageHandlerInterface *m_handler; + void sendTextMessageRequested(const QString &message); }; QT_END_NAMESPACE -#endif // QMLWEBVIEWTRANSPORT_H +#endif // TESTTRANSPORT_H diff --git a/tests/auto/qml/tst_bench.qml b/tests/auto/qml/tst_bench.qml index 056dda9..833720e 100644 --- a/tests/auto/qml/tst_bench.qml +++ b/tests/auto/qml/tst_bench.qml @@ -40,13 +40,24 @@ ****************************************************************************/ import QtQuick 2.0 +import QtTest 1.0 import QtWebChannel 1.0 +import "qrc:///qwebchannel/qwebchannel.js" as Client -WebChannelTest { +TestCase { name: "Bench" id: test + Client { + id: client + } + + WebChannel { + id: webChannel + transports: [client.serverTransport] + } + Component { id: component QtObject { @@ -96,28 +107,15 @@ WebChannelTest { webChannel.registerObjects(objects); } - function benchmark_init_baseline() - { - loadUrl("bench_init.html"); - } - - function benchmark_init_webview() + function cleanup() { - useWebViewTransport = true; - loadUrl("bench_init.html"); - // init - awaitMessage(); - // idle - awaitMessage(); + client.cleanup(); } - function benchmark_init_websocket() + function benchmark_init() { - useWebViewTransport = false; - loadUrl("bench_init.html"); - // init - awaitMessage(); - // idle - awaitMessage(); + var channel = client.createChannel(function() {}); + client.awaitInit(); + client.awaitIdle(); } } diff --git a/tests/auto/qml/tst_metaobjectpublisher.qml b/tests/auto/qml/tst_metaobjectpublisher.qml index e07f105..880b30d 100644 --- a/tests/auto/qml/tst_metaobjectpublisher.qml +++ b/tests/auto/qml/tst_metaobjectpublisher.qml @@ -40,12 +40,17 @@ ****************************************************************************/ import QtQuick 2.0 +import QtTest 1.0 import QtWebChannel 1.0 +import "qrc:///qwebchannel/qwebchannel.js" as Client -WebChannelTest { +TestCase { name: "MetaObjectPublisher" - id: test + + Client { + id: client + } property var lastMethodArg @@ -90,62 +95,71 @@ WebChannelTest { } } - function initTestCase() - { - webChannel.registeredObjects = [myObj, myOtherObj, myFactory]; + WebChannel { + id: webChannel + transports: [client.serverTransport] + registeredObjects: [myObj, myOtherObj, myFactory] } - function awaitMessageSkipIdle() + function init() { - var msg; - do { - msg = awaitMessage(); - verify(msg); - verify(msg.data); - } while (msg.data.type === qWebChannelMessageTypes.idle); - return msg; + myObj.myProperty = 1 + client.cleanup(); } function test_property() { - myObj.myProperty = 1 - loadUrl("property.html"); - awaitInit(); - var msg = awaitMessageSkipIdle(); + 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 = awaitMessageSkipIdle(); + 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 = awaitMessageSkipIdle(); + msg = client.awaitMessageSkipIdle(); compare(myObj.myProperty, 3); // the above write is also propagated to the HTML client - msg = awaitMessageSkipIdle(); + msg = client.awaitMessageSkipIdle(); compare(msg.data.label, "changed"); compare(msg.data.value, 3); - awaitIdle(); + client.awaitIdle(); } function test_method() { - loadUrl("method.html"); - awaitInit(); - awaitIdle(); + 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 = awaitMessage(); - compare(msg.data.type, qWebChannelMessageTypes.invokeMethod); + var msg = client.awaitMessage(); + compare(msg.data.type, Client.QWebChannelMessageTypes.invokeMethod); compare(msg.data.object, "myObj"); compare(msg.data.args, ["test"]); @@ -154,27 +168,35 @@ WebChannelTest { function test_signal() { - loadUrl("signal.html"); - awaitInit(); - - var msg = awaitMessage(); - compare(msg.data.type, qWebChannelMessageTypes.connectToSignal); + 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, Client.QWebChannelMessageTypes.connectToSignal); compare(msg.data.object, "myObj"); - awaitIdle(); + client.awaitIdle(); myObj.mySignal("test"); - msg = awaitMessage(); + msg = client.awaitMessageSkipIdle(); compare(msg.data.label, "signalReceived"); compare(msg.data.value, "test"); } function test_grouping() { - loadUrl("grouping.html"); - awaitInit(); - awaitIdle(); + var channel = client.createChannel(function(channel) { + channel.subscribe(Client.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) { @@ -183,81 +205,106 @@ WebChannelTest { myOtherObj.bar = i; } - var msg = awaitMessage(); + var msg = client.awaitMessage(); verify(msg); compare(msg.data.label, "gotPropertyUpdate"); compare(msg.data.values, [myObj.myProperty, myOtherObj.foo, myOtherObj.bar]); - awaitIdle(); + client.awaitIdle(); } function test_wrapper() { - loadUrl("wrapper.html"); - awaitInit(); - - var msg = awaitMessageSkipIdle(); - compare(msg.data.type, qWebChannelMessageTypes.invokeMethod); + 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, Client.QWebChannelMessageTypes.invokeMethod); compare(msg.data.object, "myFactory"); verify(myFactory.lastObj); compare(myFactory.lastObj.objectName, "testObj"); - msg = awaitMessageSkipIdle(); - compare(msg.data.type, qWebChannelMessageTypes.connectToSignal); + msg = client.awaitMessageSkipIdle(); + compare(msg.data.type, Client.QWebChannelMessageTypes.connectToSignal); verify(msg.data.object); var objId = msg.data.object; - msg = awaitMessageSkipIdle(); - compare(msg.data.type, qWebChannelMessageTypes.connectToSignal); + msg = client.awaitMessageSkipIdle(); + compare(msg.data.type, Client.QWebChannelMessageTypes.connectToSignal); compare(msg.data.object, objId); - msg = awaitMessageSkipIdle(); - compare(msg.data.type, qWebChannelMessageTypes.setProperty); + msg = client.awaitMessageSkipIdle(); + compare(msg.data.type, Client.QWebChannelMessageTypes.setProperty); compare(msg.data.object, objId); compare(myFactory.lastObj.myProperty, 42); - msg = awaitMessageSkipIdle(); - compare(msg.data.type, qWebChannelMessageTypes.invokeMethod); + msg = client.awaitMessageSkipIdle(); + compare(msg.data.type, Client.QWebChannelMessageTypes.invokeMethod); compare(msg.data.object, objId); compare(msg.data.args, ["foobar"]); - msg = awaitMessageSkipIdle(); + 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 = awaitMessageSkipIdle(); - compare(msg.data.type, qWebChannelMessageTypes.invokeMethod); + msg = client.awaitMessageSkipIdle(); + compare(msg.data.type, Client.QWebChannelMessageTypes.invokeMethod); compare(msg.data.object, objId); + client.awaitIdle(); + webChannel.sendMessage("report"); - msg = awaitMessageSkipIdle(); + msg = client.awaitMessageSkipIdle(); compare(msg.data.label, "report"); compare(msg.data.obj, {}); } function test_disconnect() { - loadUrl("disconnect.html"); - awaitInit(); - - var msg = awaitMessage(); - compare(msg.data.type, qWebChannelMessageTypes.connectToSignal); + 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, Client.QWebChannelMessageTypes.connectToSignal); compare(msg.data.object, "myObj"); - awaitIdle(); + client.awaitIdle(); myObj.mySignal(42); - msg = awaitMessage(); + msg = client.awaitMessage(); compare(msg.data.label, "mySignalReceived"); compare(msg.data.args, [42]); - msg = awaitMessage(); - compare(msg.data.type, qWebChannelMessageTypes.disconnectFromSignal); + msg = client.awaitMessage(); + compare(msg.data.type, Client.QWebChannelMessageTypes.disconnectFromSignal); compare(msg.data.object, "myObj"); myObj.mySignal(0); @@ -266,7 +313,7 @@ WebChannelTest { // and verify no mySignalReceived was triggered by the above emission webChannel.sendMessage("report"); - msg = awaitMessage(); + 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 519a63c..6c4b9b7 100644 --- a/tests/auto/qml/tst_multiclient.qml +++ b/tests/auto/qml/tst_multiclient.qml @@ -40,12 +40,21 @@ ****************************************************************************/ import QtQuick 2.0 +import QtTest 1.0 import QtWebChannel 1.0 +import "qrc:///qwebchannel/qwebchannel.js" as Client -WebChannelTest { +TestCase { name: "MultiClient" - id: test + + Client { + id: client1 + } + + Client { + id: client2 + } QtObject { id: foo @@ -61,39 +70,50 @@ WebChannelTest { WebChannel.id: "foo" } - TestWebView { - id: client1 + WebChannel { + id: webChannel + transports: [client1.serverTransport, client2.serverTransport] + registeredObjects: [foo] } - TestWebView { - id: client2 + + function init() + { + client1.cleanup(); + client2.cleanup(); } - function initTestCase() + function clientInitCallback(channel) { - webChannel.registeredObjects = [foo]; + channel.objects.foo.ping.connect(function() { + channel.objects.foo.pong(function(value) { + channel.exec({pongAnswer: value}); + }); + }); } function test_multiclient() { - loadUrl("multiclient.html", client1); - loadUrl("multiclient.html", client2); + var c1 = client1.createChannel(clientInitCallback); + var c2 = client2.createChannel(clientInitCallback); + // init, connect & idle messages for two clients - for (var i = 0; i < 3 * 2; ++i) { - awaitMessage(); + for (var i = 0; i < 3; ++i) { + client1.awaitMessage(); + client2.awaitMessage(); } foo.ping(); // invoke of pong method - awaitMessage(); - awaitMessage(); + client1.awaitMessage(); + client2.awaitMessage(); - var msg = awaitMessage(); + var msg = client1.awaitMessage(); compare(msg.data.pongAnswer, 1); - msg = awaitMessage(); + msg = client2.awaitMessage(); compare(msg.data.pongAnswer, 2); - awaitIdle(); - awaitIdle(); + client1.awaitIdle(); + client2.awaitIdle(); } } diff --git a/tests/auto/qml/tst_webchannel.qml b/tests/auto/qml/tst_webchannel.qml index dddba71..3c404d3 100644 --- a/tests/auto/qml/tst_webchannel.qml +++ b/tests/auto/qml/tst_webchannel.qml @@ -40,20 +40,47 @@ ****************************************************************************/ import QtQuick 2.0 +import QtTest 1.0 -WebChannelTest { +import QtWebChannel 1.0 +import "qrc:///qwebchannel/qwebchannel.js" as Client + +TestCase { name: "WebChannel" + Client { + id: client + } + + WebChannel { + id: webChannel + transports: [client.serverTransport] + } + + function cleanup() + { + client.cleanup(); + } + function test_receiveRawMessage() { - loadUrl("receiveRaw.html"); - compare(awaitRawMessage(), "foobar"); + var channel = client.createChannel(function (channel) { + channel.send("foobar"); + }, true /* raw */); + compare(client.awaitRawMessage(), "foobar"); } function test_sendMessage() { - loadUrl("send.html"); + var channel = client.createChannel(function (channel) { + channel.subscribe("myMessage", function(payload) { + channel.send("myMessagePong:" + payload); + }); + channel.send("initialized"); + }, true /* raw */); + + compare(client.awaitRawMessage(), "initialized"); webChannel.sendMessage("myMessage", "foobar"); - compare(awaitRawMessage(), "myMessagePong:foobar"); + compare(client.awaitRawMessage(), "myMessagePong:foobar"); } } diff --git a/tests/auto/webchannel/tst_webchannel.cpp b/tests/auto/webchannel/tst_webchannel.cpp index ece02a8..10baaa3 100644 --- a/tests/auto/webchannel/tst_webchannel.cpp +++ b/tests/auto/webchannel/tst_webchannel.cpp @@ -44,7 +44,6 @@ #include <qwebchannel.h> #include <qwebchannel_p.h> #include <qmetaobjectpublisher_p.h> -#include <qwebsockettransport.h> #include <QtTest> @@ -76,20 +75,6 @@ void TestWebChannel::setVariant(const QVariant &v) m_lastVariant = v; } -void TestWebChannel::testInitWebSocketTransport() -{ - QWebSocketTransport transport; - QSignalSpy initSpy(&transport, SIGNAL(initialized())); - QSignalSpy baseUrlSpy(&transport, SIGNAL(baseUrlChanged(QString))); - - QVERIFY(initSpy.wait()); - QCOMPARE(initSpy.size(), 1); - QCOMPARE(baseUrlSpy.size(), 1); - QCOMPARE(baseUrlSpy.first().size(), 1); - QCOMPARE(transport.baseUrl(), baseUrlSpy.first().first().toString()); - QVERIFY(!transport.baseUrl().isEmpty()); -} - void TestWebChannel::testRegisterObjects() { QWebChannel channel; diff --git a/tests/auto/webchannel/tst_webchannel.h b/tests/auto/webchannel/tst_webchannel.h index 314fe99..4811a5c 100644 --- a/tests/auto/webchannel/tst_webchannel.h +++ b/tests/auto/webchannel/tst_webchannel.h @@ -44,24 +44,22 @@ #include <QObject> #include <QVariant> -#include <qwebchanneltransportinterface.h> -class DummyTransport : public QObject, public QWebChannelTransportInterface +#include <QtWebChannel/QWebChannelAbstractTransport> + +class DummyTransport : public QWebChannelAbstractTransport { Q_OBJECT - Q_INTERFACES(QWebChannelTransportInterface) public: explicit DummyTransport(QObject *parent) - : QObject(parent) + : QWebChannelAbstractTransport(parent) {} ~DummyTransport() {}; - void sendMessage(const QString &/*message*/, int /*clientId*/) const Q_DECL_OVERRIDE - {} - void sendMessage(const QByteArray &/*message*/, int /*clientId*/) const Q_DECL_OVERRIDE - {} - void setMessageHandler(QWebChannelMessageHandlerInterface * /*handler*/) Q_DECL_OVERRIDE - {} +public slots: + void sendTextMessage(const QString &/*message*/) Q_DECL_OVERRIDE + { + } }; class TestObject : public QObject @@ -216,7 +214,6 @@ public: Q_INVOKABLE void setVariant(const QVariant &v); private slots: - void testInitWebSocketTransport(); void testRegisterObjects(); void testInfoForObject(); void testInvokeMethodConversion(); |