diff options
-rw-r--r-- | .qmake.conf | 2 | ||||
-rw-r--r-- | examples/webchannel/shared/websockettransport.cpp | 4 | ||||
-rw-r--r-- | src/webchannel/qmetaobjectpublisher.cpp | 9 | ||||
-rw-r--r-- | src/webchannel/qwebchannel.h | 4 | ||||
-rw-r--r-- | src/webchannel/qwebchannel.js | 3 | ||||
-rw-r--r-- | src/webchannel/qwebchannelabstracttransport.h | 2 | ||||
-rw-r--r-- | tests/auto/webchannel/tst_webchannel.cpp | 261 | ||||
-rw-r--r-- | tests/auto/webchannel/tst_webchannel.h | 22 | ||||
-rw-r--r-- | tests/auto/webchannel/webchannel.pro | 5 |
9 files changed, 303 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 22df02b..324191e 100644 --- a/src/webchannel/qmetaobjectpublisher.cpp +++ b/src/webchannel/qmetaobjectpublisher.cpp @@ -451,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); @@ -487,6 +489,7 @@ QJsonValue QMetaObjectPublisher::wrapResult(const QVariant &result, QWebChannelA objectInfo[KEY_ID] = id; if (!classInfo.isEmpty()) objectInfo[KEY_DATA] = classInfo; + return objectInfo; #ifndef QT_NO_JSVALUE } else if (result.canConvert<QJSValue>()) { 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/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 +} |