summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.qmake.conf2
-rw-r--r--examples/webchannel/shared/websockettransport.cpp4
-rw-r--r--src/webchannel/qmetaobjectpublisher.cpp13
-rw-r--r--src/webchannel/qwebchannel.h4
-rw-r--r--src/webchannel/qwebchannel.js3
-rw-r--r--src/webchannel/qwebchannelabstracttransport.h2
-rw-r--r--src/webchannel/webchannel.pro2
-rw-r--r--tests/auto/webchannel/tst_webchannel.cpp261
-rw-r--r--tests/auto/webchannel/tst_webchannel.h22
-rw-r--r--tests/auto/webchannel/webchannel.pro5
10 files changed, 309 insertions, 9 deletions
diff --git a/.qmake.conf b/.qmake.conf
index 75ecd52..76d721c 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -1,4 +1,4 @@
load(qt_build_config)
CONFIG += qt_example_installs warning_clean
-MODULE_VERSION = 5.5.1
+MODULE_VERSION = 5.6.0
diff --git a/examples/webchannel/shared/websockettransport.cpp b/examples/webchannel/shared/websockettransport.cpp
index 8ed330c..f484a24 100644
--- a/examples/webchannel/shared/websockettransport.cpp
+++ b/examples/webchannel/shared/websockettransport.cpp
@@ -60,6 +60,8 @@ WebSocketTransport::WebSocketTransport(QWebSocket *socket)
{
connect(socket, &QWebSocket::textMessageReceived,
this, &WebSocketTransport::textMessageReceived);
+ connect(socket, &QWebSocket::disconnected,
+ this, &WebSocketTransport::deleteLater);
}
/*!
@@ -67,7 +69,7 @@ WebSocketTransport::WebSocketTransport(QWebSocket *socket)
*/
WebSocketTransport::~WebSocketTransport()
{
-
+ m_socket->deleteLater();
}
/*!
diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp
index 0cad569..61c5144 100644
--- a/src/webchannel/qmetaobjectpublisher.cpp
+++ b/src/webchannel/qmetaobjectpublisher.cpp
@@ -41,7 +41,9 @@
#include <QDebug>
#include <QJsonObject>
#include <QJsonArray>
+#ifdef QT_HAVE_QML
#include <QJSValue>
+#endif
#include <QUuid>
QT_BEGIN_NAMESPACE
@@ -449,16 +451,18 @@ QJsonValue QMetaObjectPublisher::wrapResult(const QVariant &result, QWebChannelA
{
if (QObject *object = result.value<QObject *>()) {
QString id = registeredObjectIds.value(object);
+
QJsonObject classInfo;
if (id.isEmpty()) {
// neither registered, nor wrapped, do so now
id = QUuid::createUuid().toString();
- Q_ASSERT(!registeredObjects.contains(id));
+ // store ID before the call to classInfoForObject()
+ // in case of self-contained objects it avoids
+ // infinite loops
+ registeredObjectIds[object] = id;
classInfo = classInfoForObject(object, transport);
- registeredObjectIds[object] = id;
-
ObjectInfo oi(object, classInfo);
if (transport) {
oi.transports.append(transport);
@@ -485,13 +489,16 @@ QJsonValue QMetaObjectPublisher::wrapResult(const QVariant &result, QWebChannelA
objectInfo[KEY_ID] = id;
if (!classInfo.isEmpty())
objectInfo[KEY_DATA] = classInfo;
+
return objectInfo;
+#ifdef QT_HAVE_QML
} else if (result.canConvert<QJSValue>()) {
// Workaround for keeping QJSValues from QVariant.
// Calling QJSValue::toVariant() converts JS-objects/arrays to QVariantMap/List
// instead of stashing a QJSValue itself into a variant.
// TODO: Improve QJSValue-QJsonValue conversion in Qt.
return wrapResult(result.value<QJSValue>().toVariant(), transport, parentObjectId);
+#endif
} else if (result.canConvert<QVariantList>()) {
// recurse and potentially wrap contents of the array
return wrapList(result.toList(), transport);
diff --git a/src/webchannel/qwebchannel.h b/src/webchannel/qwebchannel.h
index 0e642db..d32d017 100644
--- a/src/webchannel/qwebchannel.h
+++ b/src/webchannel/qwebchannel.h
@@ -35,8 +35,8 @@
#ifndef QWEBCHANNEL_H
#define QWEBCHANNEL_H
-#include <QObject>
-#include <QJsonValue>
+#include <QtCore/QObject>
+#include <QtCore/QJsonValue>
#include <QtWebChannel/qwebchannelglobal.h>
diff --git a/src/webchannel/qwebchannel.js b/src/webchannel/qwebchannel.js
index c270a95..d8c28bc 100644
--- a/src/webchannel/qwebchannel.js
+++ b/src/webchannel/qwebchannel.js
@@ -193,7 +193,7 @@ function QObject(name, data, webChannel)
}
if (!response
|| !response["__QObject*__"]
- || response["id"] === undefined) {
+ || response.id === undefined) {
return response;
}
@@ -365,6 +365,7 @@ function QObject(name, data, webChannel)
}
Object.defineProperty(object, propertyName, {
+ configurable: true,
get: function () {
var propertyValue = object.__propertyCache__[propertyIndex];
if (propertyValue === undefined) {
diff --git a/src/webchannel/qwebchannelabstracttransport.h b/src/webchannel/qwebchannelabstracttransport.h
index 851c622..e7f2af4 100644
--- a/src/webchannel/qwebchannelabstracttransport.h
+++ b/src/webchannel/qwebchannelabstracttransport.h
@@ -34,7 +34,7 @@
#ifndef QWEBCHANNELABSTRACTTRANSPORT_H
#define QWEBCHANNELABSTRACTTRANSPORT_H
-#include <QObject>
+#include <QtCore/QObject>
#include <QtWebChannel/qwebchannelglobal.h>
QT_BEGIN_NAMESPACE
diff --git a/src/webchannel/webchannel.pro b/src/webchannel/webchannel.pro
index eba8123..edaac13 100644
--- a/src/webchannel/webchannel.pro
+++ b/src/webchannel/webchannel.pro
@@ -30,6 +30,8 @@ SOURCES += \
qtHaveModule(qml) {
QT += qml
+ DEFINES += "QT_HAVE_QML"
+
SOURCES += \
qqmlwebchannel.cpp \
qqmlwebchannelattached.cpp
diff --git a/tests/auto/webchannel/tst_webchannel.cpp b/tests/auto/webchannel/tst_webchannel.cpp
index 55fd2d9..f1911e5 100644
--- a/tests/auto/webchannel/tst_webchannel.cpp
+++ b/tests/auto/webchannel/tst_webchannel.cpp
@@ -38,9 +38,156 @@
#include <qmetaobjectpublisher_p.h>
#include <QtTest>
+#ifdef WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
+#include <QJSEngine>
+#endif
QT_USE_NAMESPACE
+#ifdef WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
+class TestJSEngine;
+
+class TestEngineTransport : public QWebChannelAbstractTransport
+{
+ Q_OBJECT
+public:
+ TestEngineTransport(TestJSEngine *);
+ void sendMessage(const QJsonObject &message) Q_DECL_OVERRIDE;
+
+ Q_INVOKABLE void channelSetupReady();
+ Q_INVOKABLE void send(const QByteArray &message);
+private:
+ TestJSEngine *m_testEngine;
+};
+
+class ConsoleLogger : public QObject
+{
+ Q_OBJECT
+public:
+ ConsoleLogger(QObject *parent = 0);
+
+ Q_INVOKABLE void log(const QString &text);
+ Q_INVOKABLE void error(const QString &text);
+
+ int errorCount() const { return m_errCount; }
+ int logCount() const { return m_logCount; }
+ QString lastError() const { return m_lastError; }
+
+private:
+ int m_errCount;
+ int m_logCount;
+ QString m_lastError;
+
+};
+
+
+
+ConsoleLogger::ConsoleLogger(QObject *parent)
+ : QObject(parent)
+ , m_errCount(0)
+ , m_logCount(0)
+{
+}
+
+void ConsoleLogger::log(const QString &text)
+{
+ m_logCount++;
+ qDebug("LOG: %s", qPrintable(text));
+}
+
+void ConsoleLogger::error(const QString &text)
+{
+ m_errCount++;
+ m_lastError = text;
+ qWarning("ERROR: %s", qPrintable(text));
+}
+
+
+// A test JS engine with convenience integration with WebChannel.
+class TestJSEngine : public QJSEngine
+{
+ Q_OBJECT
+public:
+ TestJSEngine();
+
+ TestEngineTransport *transport() const;
+ ConsoleLogger *logger() const;
+ void initWebChannelJS();
+
+signals:
+ void channelSetupReady(TestEngineTransport *transport);
+
+private:
+ TestEngineTransport *m_transport;
+ ConsoleLogger *m_logger;
+};
+
+TestEngineTransport::TestEngineTransport(TestJSEngine *engine)
+ : QWebChannelAbstractTransport(engine)
+ , m_testEngine(engine)
+{
+}
+
+void TestEngineTransport::sendMessage(const QJsonObject &message)
+{
+ QByteArray json = QJsonDocument(message).toJson(QJsonDocument::Compact);
+ QJSValue callback = m_testEngine->evaluate(QStringLiteral("transport.onmessage"));
+ Q_ASSERT(callback.isCallable());
+ QJSValue arg = m_testEngine->newObject();
+ QJSValue data = m_testEngine->evaluate(QString::fromLatin1("JSON.parse('%1');").arg(QString::fromUtf8(json)));
+ Q_ASSERT(!data.isError());
+ arg.setProperty(QStringLiteral("data"), data);
+ QJSValue val = callback.call((QJSValueList() << arg));
+ Q_ASSERT(!val.isError());
+}
+
+void TestEngineTransport::channelSetupReady()
+{
+ emit m_testEngine->channelSetupReady(m_testEngine->transport());
+}
+
+void TestEngineTransport::send(const QByteArray &message)
+{
+ QJsonDocument doc(QJsonDocument::fromJson(message));
+ emit messageReceived(doc.object(), this);
+}
+
+
+TestJSEngine::TestJSEngine()
+ : m_transport(new TestEngineTransport(this))
+ , m_logger(new ConsoleLogger(this))
+{
+ globalObject().setProperty("transport", newQObject(m_transport));
+ globalObject().setProperty("console", newQObject(m_logger));
+
+ QString webChannelJSPath(QStringLiteral(":/qtwebchannel/qwebchannel.js"));
+ QFile webChannelJS(webChannelJSPath);
+ if (!webChannelJS.open(QFile::ReadOnly))
+ qFatal("Error opening qwebchannel.js");
+ QString source(QString::fromUtf8(webChannelJS.readAll()));
+ evaluate(source, webChannelJSPath);
+}
+
+TestEngineTransport *TestJSEngine::transport() const
+{
+ return m_transport;
+}
+
+ConsoleLogger *TestJSEngine::logger() const
+{
+ return m_logger;
+}
+
+void TestJSEngine::initWebChannelJS()
+{
+ globalObject().setProperty(QStringLiteral("channel"), newObject());
+ QJSValue channel = evaluate(QStringLiteral("channel = new QWebChannel(transport, function(channel) { transport.channelSetupReady();});"));
+ Q_ASSERT(!channel.isError());
+}
+
+#endif // WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
+
+
TestWebChannel::TestWebChannel(QObject *parent)
: QObject(parent)
, m_dummyTransport(new DummyTransport(this))
@@ -148,6 +295,12 @@ void TestWebChannel::testInfoForObject()
}
{
QJsonArray method;
+ method.append(QStringLiteral("setObjectProperty"));
+ method.append(obj.metaObject()->indexOfMethod("setObjectProperty(QObject*)"));
+ expected.append(method);
+ }
+ {
+ QJsonArray method;
method.append(QStringLiteral("method1"));
method.append(obj.metaObject()->indexOfMethod("method1()"));
expected.append(method);
@@ -230,6 +383,19 @@ void TestWebChannel::testInfoForObject()
property.append(obj.bar());
expected.append(property);
}
+ {
+ QJsonArray property;
+ property.append(obj.metaObject()->indexOfProperty("objectProperty"));
+ property.append(QStringLiteral("objectProperty"));
+ {
+ QJsonArray signal;
+ signal.append(1);
+ signal.append(obj.metaObject()->indexOfMethod("objectPropertyChanged()"));
+ property.append(signal);
+ }
+ property.append(QJsonValue::fromVariant(QVariant::fromValue(obj.objectProperty())));
+ expected.append(property);
+ }
QCOMPARE(info["properties"].toArray(), expected);
}
}
@@ -270,6 +436,43 @@ void TestWebChannel::testDisconnect()
m_dummyTransport->emitMessageReceived(QJsonObject());
}
+void TestWebChannel::testWrapRegisteredObject()
+{
+ QWebChannel channel;
+ TestObject obj;
+ obj.setObjectName("myTestObject");
+
+ channel.registerObject(obj.objectName(), &obj);
+ channel.connectTo(m_dummyTransport);
+ channel.d_func()->publisher->initializeClient(m_dummyTransport);
+
+ QJsonObject objectInfo = channel.d_func()->publisher->wrapResult(QVariant::fromValue(&obj), m_dummyTransport).toObject();
+
+ QCOMPARE(2, objectInfo.length());
+ QVERIFY(objectInfo.contains("id"));
+ QVERIFY(objectInfo.contains("__QObject*__"));
+ QVERIFY(objectInfo.value("__QObject*__").isBool() && objectInfo.value("__QObject*__").toBool());
+
+ QString returnedId = objectInfo.value("id").toString();
+
+ QCOMPARE(&obj, channel.d_func()->publisher->registeredObjects.value(obj.objectName()));
+ QCOMPARE(obj.objectName(), channel.d_func()->publisher->registeredObjectIds.value(&obj));
+ QCOMPARE(obj.objectName(), returnedId);
+}
+
+void TestWebChannel::testInfiniteRecursion()
+{
+ QWebChannel channel;
+ TestObject obj;
+ obj.setObjectProperty(&obj);
+ obj.setObjectName("myTestObject");
+
+ channel.connectTo(m_dummyTransport);
+ channel.d_func()->publisher->initializeClient(m_dummyTransport);
+
+ QJsonObject objectInfo = channel.d_func()->publisher->wrapResult(QVariant::fromValue(&obj), m_dummyTransport).toObject();
+}
+
static QHash<QString, QObject*> createObjects(QObject *parent)
{
const int num = 100;
@@ -352,5 +555,63 @@ void TestWebChannel::benchRegisterObjects()
channel.registerObjects(objects);
}
}
+#ifdef WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
+class SubclassedTestObject : public TestObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString bar READ bar WRITE setBar NOTIFY theBarHasChanged)
+public:
+ void setBar(const QString &newBar);
+signals:
+ void theBarHasChanged();
+};
+
+void SubclassedTestObject::setBar(const QString &newBar)
+{
+ if (!newBar.isNull())
+ emit theBarHasChanged();
+}
+
+class TestSubclassedFunctor {
+public:
+ TestSubclassedFunctor(TestJSEngine *engine)
+ : m_engine(engine)
+ {
+ }
+
+ void operator()() {
+ QCOMPARE(m_engine->logger()->errorCount(), 0);
+ }
+
+private:
+ TestJSEngine *m_engine;
+};
+#endif // WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
+
+void TestWebChannel::qtbug46548_overriddenProperties()
+{
+#ifndef WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
+ QSKIP("A JS engine is required for this test to make sense.");
+#else
+ SubclassedTestObject obj;
+ obj.setObjectName("subclassedTestObject");
+
+ QWebChannel webChannel;
+ webChannel.registerObject(obj.objectName(), &obj);
+ TestJSEngine engine;
+ webChannel.connectTo(engine.transport());
+ QSignalSpy spy(&engine, &TestJSEngine::channelSetupReady);
+ connect(&engine, &TestJSEngine::channelSetupReady, TestSubclassedFunctor(&engine));
+ engine.initWebChannelJS();
+ if (!spy.count())
+ spy.wait();
+ QCOMPARE(spy.count(), 1);
+ QJSValue subclassedTestObject = engine.evaluate("channel.objects[\"subclassedTestObject\"]");
+ QVERIFY(subclassedTestObject.isObject());
+
+#endif // WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
+}
QTEST_MAIN(TestWebChannel)
+
+#include "tst_webchannel.moc"
diff --git a/tests/auto/webchannel/tst_webchannel.h b/tests/auto/webchannel/tst_webchannel.h
index 7b9a1e3..b2a1040 100644
--- a/tests/auto/webchannel/tst_webchannel.h
+++ b/tests/auto/webchannel/tst_webchannel.h
@@ -69,9 +69,12 @@ class TestObject : public QObject
Q_PROPERTY(Foo foo READ foo CONSTANT)
Q_PROPERTY(int asdf READ asdf NOTIFY asdfChanged)
Q_PROPERTY(QString bar READ bar NOTIFY theBarHasChanged)
+ Q_PROPERTY(QObject * objectProperty READ objectProperty WRITE setObjectProperty NOTIFY objectPropertyChanged)
+
public:
explicit TestObject(QObject *parent = 0)
: QObject(parent)
+ , mObjectProperty(0)
{ }
enum Foo {
@@ -83,6 +86,11 @@ public:
int asdf() const {return 42;}
QString bar() const {return QString();}
+ QObject *objectProperty() const
+ {
+ return mObjectProperty;
+ }
+
Q_INVOKABLE void method1() {}
protected:
@@ -96,16 +104,26 @@ signals:
void sig2(const QString&);
void asdfChanged();
void theBarHasChanged();
+ void objectPropertyChanged();
public slots:
void slot1() {}
void slot2(const QString&) {}
+ void setObjectProperty(QObject *object)
+ {
+ mObjectProperty = object;
+ emit objectPropertyChanged();
+ }
+
protected slots:
void slot3() {}
private slots:
void slot4() {}
+
+public:
+ QObject *mObjectProperty;
};
class BenchObject : public QObject
@@ -218,12 +236,16 @@ private slots:
void testInfoForObject();
void testInvokeMethodConversion();
void testDisconnect();
+ void testWrapRegisteredObject();
+ void testInfiniteRecursion();
void benchClassInfo();
void benchInitializeClients();
void benchPropertyUpdates();
void benchRegisterObjects();
+ void qtbug46548_overriddenProperties();
+
private:
DummyTransport *m_dummyTransport;
diff --git a/tests/auto/webchannel/webchannel.pro b/tests/auto/webchannel/webchannel.pro
index 40b324e..2dcc610 100644
--- a/tests/auto/webchannel/webchannel.pro
+++ b/tests/auto/webchannel/webchannel.pro
@@ -12,3 +12,8 @@ SOURCES += \
HEADERS += \
tst_webchannel.h
+
+qtHaveModule(qml) {
+ DEFINES += WEBCHANNEL_TESTS_CAN_USE_JS_ENGINE
+ QT += qml
+}