diff options
-rw-r--r-- | examples/qtobject/main.qml | 13 | ||||
-rw-r--r-- | examples/standalone/main.cpp | 9 | ||||
-rw-r--r-- | src/imports/webchannel/plugin.cpp | 6 | ||||
-rw-r--r-- | src/imports/webchannel/qmlwebchannel.cpp | 75 | ||||
-rw-r--r-- | src/imports/webchannel/qmlwebchannel.h | 63 | ||||
-rw-r--r-- | src/imports/webchannel/webchannel.pro | 11 | ||||
-rw-r--r-- | src/webchannel/qmetaobjectpublisher.cpp | 344 | ||||
-rw-r--r-- | src/webchannel/qmetaobjectpublisher.h | 121 | ||||
-rw-r--r-- | src/webchannel/qmetaobjectpublisher_p.h | 62 | ||||
-rw-r--r-- | src/webchannel/qwebchannel.cpp | 149 | ||||
-rw-r--r-- | src/webchannel/qwebchannel.h | 35 | ||||
-rw-r--r-- | src/webchannel/qwebchannel_p.h | 60 | ||||
-rw-r--r-- | src/webchannel/qwebchannelsocket.cpp | 99 | ||||
-rw-r--r-- | src/webchannel/qwebchannelsocket_p.h | 74 | ||||
-rw-r--r-- | src/webchannel/signalhandler_p.h | 2 | ||||
-rw-r--r-- | src/webchannel/webchannel.pro | 9 | ||||
-rw-r--r-- | tests/qml/tst_bench.qml | 14 | ||||
-rw-r--r-- | tests/qml/tst_metaobjectpublisher.qml | 21 | ||||
-rw-r--r-- | tests/webchannel/tst_webchannel.cpp | 80 |
19 files changed, 711 insertions, 536 deletions
diff --git a/examples/qtobject/main.qml b/examples/qtobject/main.qml index 76ef3d9..7bfe714 100644 --- a/examples/qtobject/main.qml +++ b/examples/qtobject/main.qml @@ -55,22 +55,11 @@ Rectangle { objectName: "initialTestObject" } - MetaObjectPublisher { - id: publisher - webChannel: webChannel - } - WebChannel { id: webChannel - onRawMessageReceived: { - if (!publisher.handleRawMessage(rawMessage)) { - console.log("unhandled request: ", rawMessage); - } - } - onInitialized: { - publisher.registerObjects({ + registerObjects({ "testObjectFactory": factory, "initialTestObject": testObject }); diff --git a/examples/standalone/main.cpp b/examples/standalone/main.cpp index 6970633..1187950 100644 --- a/examples/standalone/main.cpp +++ b/examples/standalone/main.cpp @@ -41,7 +41,6 @@ ****************************************************************************/ #include "qwebchannel.h" -#include "qmetaobjectpublisher.h" #include <QApplication> #include <QDialog> @@ -111,16 +110,10 @@ int main(int argc, char** argv) QApplication app(argc, argv); QWebChannel channel; - QMetaObjectPublisher publisher; - publisher.setWebChannel(&channel); - QObject::connect(&channel, SIGNAL(rawMessageReceived(QString)), - &publisher, SLOT(handleRawMessage(QString))); Dialog dialog(&channel); - QVariantMap objects; - objects[QStringLiteral("dialog")] = QVariant::fromValue(&dialog); - publisher.registerObjects(objects); + channel.registerObject(QStringLiteral("dialog"), &dialog); return app.exec(); } diff --git a/src/imports/webchannel/plugin.cpp b/src/imports/webchannel/plugin.cpp index a5e9895..0835c2b 100644 --- a/src/imports/webchannel/plugin.cpp +++ b/src/imports/webchannel/plugin.cpp @@ -43,8 +43,7 @@ #include <qqml.h> #include <QtQml/QQmlExtensionPlugin> -#include "qwebchannel.h" -#include "qmetaobjectpublisher.h" +#include "qmlwebchannel.h" QT_USE_NAMESPACE @@ -61,8 +60,7 @@ void QWebChannelPlugin::registerTypes(const char *uri) { int major = 1; int minor = 0; - qmlRegisterType<QWebChannel>(uri, major, minor, "WebChannel"); - qmlRegisterType<QMetaObjectPublisher>(uri, major, minor, "MetaObjectPublisher"); + qmlRegisterType<QmlWebChannel>(uri, major, minor, "WebChannel"); } diff --git a/src/imports/webchannel/qmlwebchannel.cpp b/src/imports/webchannel/qmlwebchannel.cpp new file mode 100644 index 0000000..a7a6db0 --- /dev/null +++ b/src/imports/webchannel/qmlwebchannel.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 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 "qmlwebchannel.h" + +#include "qwebchannel_p.h" +#include "qmetaobjectpublisher_p.h" + +QmlWebChannel::QmlWebChannel(QObject *parent) + : QWebChannel(parent) +{ + +} + +QmlWebChannel::~QmlWebChannel() +{ + +} + +void QmlWebChannel::registerObjects(const QVariantMap &objects) +{ + QMap<QString, QVariant>::const_iterator it = objects.constBegin(); + for (; it != objects.constEnd(); ++it) { + QObject *object = it.value().value<QObject*>(); + if (!object) { + qWarning("Invalid QObject given to register under name %s", qPrintable(it.key())); + continue; + } + d->publisher->registerObject(it.key(), object); + } +} + +bool QmlWebChannel::test_clientIsIdle() const +{ + return d->publisher->clientIsIdle; +} diff --git a/src/imports/webchannel/qmlwebchannel.h b/src/imports/webchannel/qmlwebchannel.h new file mode 100644 index 0000000..b52bc8b --- /dev/null +++ b/src/imports/webchannel/qmlwebchannel.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 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 QMLWEBCHANNEL_H +#define QMLWEBCHANNEL_H + +#include <qwebchannel.h> + +class QmlWebChannel : public QWebChannel +{ + Q_OBJECT + +public: + QmlWebChannel(QObject *parent = 0); + virtual ~QmlWebChannel(); + + // TODO: replace by list property + Q_INVOKABLE void registerObjects(const QVariantMap &objects); + + // TODO: remove this by replacing QML with C++ tests + Q_INVOKABLE bool test_clientIsIdle() const; +}; + +#endif // QMLWEBCHANNEL_H diff --git a/src/imports/webchannel/webchannel.pro b/src/imports/webchannel/webchannel.pro index 1753cee..3051077 100644 --- a/src/imports/webchannel/webchannel.pro +++ b/src/imports/webchannel/webchannel.pro @@ -1,6 +1,13 @@ -QT = core quick webchannel +QT = core quick webchannel-private + +INCLUDEPATH += ../../webchannel +VPATH += ../../webchannel SOURCES += \ - plugin.cpp + plugin.cpp \ + qmlwebchannel.cpp + +HEADERS += \ + qmlwebchannel.h load(qml_plugin) diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp index 27c7d70..d3659bd 100644 --- a/src/webchannel/qmetaobjectpublisher.cpp +++ b/src/webchannel/qmetaobjectpublisher.cpp @@ -40,7 +40,6 @@ ** ****************************************************************************/ -#include "qmetaobjectpublisher.h" #include "qmetaobjectpublisher_p.h" #include "qwebchannel.h" @@ -87,8 +86,9 @@ QString objectId(const QObject *object) const int PROPERTY_UPDATE_INTERVAL = 50; } -QMetaObjectPublisherPrivate::QMetaObjectPublisherPrivate(QMetaObjectPublisher *q) - : q(q) +QMetaObjectPublisher::QMetaObjectPublisher(QWebChannel *webChannel) + : QObject(webChannel) + , webChannel(webChannel) , signalHandler(this) , clientIsIdle(false) , blockUpdates(false) @@ -97,7 +97,112 @@ QMetaObjectPublisherPrivate::QMetaObjectPublisherPrivate(QMetaObjectPublisher *q { } -void QMetaObjectPublisherPrivate::setClientIsIdle(bool isIdle) +QMetaObjectPublisher::~QMetaObjectPublisher() +{ + +} + +void QMetaObjectPublisher::registerObject(const QString &id, QObject *object) +{ + if (propertyUpdatesInitialized) { + qWarning("Registered new object after initialization. This does not work!"); + return; + } + registeredObjects[id] = object; + registeredObjectIds[object] = id; +} + +QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object) const +{ + QJsonObject data; + if (!object) { + qWarning("null object given to MetaObjectPublisher - bad API usage?"); + return data; + } + + QJsonArray qtSignals; + QJsonArray qtMethods; + QJsonArray qtProperties; + QJsonObject qtEnums; + + const QMetaObject *metaObject = object->metaObject(); + QSet<int> notifySignals; + QSet<QString> identifiers; + for (int i = 0; i < metaObject->propertyCount(); ++i) { + const QMetaProperty &prop = metaObject->property(i); + QJsonArray propertyInfo; + const QString &propertyName = QString::fromLatin1(prop.name()); + propertyInfo.append(i); + propertyInfo.append(propertyName); + identifiers << propertyName; + QJsonArray signalInfo; + if (prop.hasNotifySignal()) { + notifySignals << prop.notifySignalIndex(); + const int numParams = prop.notifySignal().parameterCount(); + if (numParams > 1) { + qWarning("Notify signal for property '%s' has %d parameters, expected zero or one.", + prop.name(), numParams); + } + // optimize: compress the common propertyChanged notification names, just send a 1 + const QByteArray ¬ifySignal = prop.notifySignal().name(); + static const QByteArray changedSuffix = QByteArrayLiteral("Changed"); + if (notifySignal.length() == changedSuffix.length() + propertyName.length() && + notifySignal.endsWith(changedSuffix) && notifySignal.startsWith(prop.name())) + { + signalInfo.append(1); + } else { + signalInfo.append(QString::fromLatin1(notifySignal)); + } + signalInfo.append(prop.notifySignalIndex()); + } else if (!prop.isConstant()) { + qWarning("Property '%s'' of object '%s' has no notify signal and is not constant, " + "value updates in HTML will be broken!", + prop.name(), object->metaObject()->className()); + } + propertyInfo.append(signalInfo); + propertyInfo.append(QJsonValue::fromVariant(prop.read(object))); + qtProperties.append(propertyInfo); + } + for (int i = 0; i < metaObject->methodCount(); ++i) { + if (notifySignals.contains(i)) { + continue; + } + const QMetaMethod &method = metaObject->method(i); + //NOTE: this must be a string, otherwise it will be converted to '{}' in QML + const QString &name = QString::fromLatin1(method.name()); + // optimize: skip overloaded methods/signals or property getters, on the JS side we can only + // call one of them anyways + // TODO: basic support for overloaded signals, methods + if (identifiers.contains(name)) { + continue; + } + identifiers << name; + // send data as array to client with format: [name, index] + QJsonArray data; + data.append(name); + data.append(i); + if (method.methodType() == QMetaMethod::Signal) { + qtSignals.append(data); + } else if (method.access() == QMetaMethod::Public) { + qtMethods.append(data); + } + } + for (int i = 0; i < metaObject->enumeratorCount(); ++i) { + QMetaEnum enumerator = metaObject->enumerator(i); + QJsonObject values; + for (int k = 0; k < enumerator.keyCount(); ++k) { + values[QString::fromLatin1(enumerator.key(k))] = enumerator.value(k); + } + qtEnums[QString::fromLatin1(enumerator.name())] = values; + } + data[KEY_SIGNALS] = qtSignals; + data[KEY_METHODS] = qtMethods; + data[KEY_PROPERTIES] = qtProperties; + data[KEY_ENUMS] = qtEnums; + return data; +} + +void QMetaObjectPublisher::setClientIsIdle(bool isIdle) { if (clientIsIdle == isIdle) { return; @@ -106,11 +211,11 @@ void QMetaObjectPublisherPrivate::setClientIsIdle(bool isIdle) if (!isIdle && timer.isActive()) { timer.stop(); } else if (isIdle && !timer.isActive()) { - timer.start(PROPERTY_UPDATE_INTERVAL, q); + timer.start(PROPERTY_UPDATE_INTERVAL, this); } } -void QMetaObjectPublisherPrivate::initializeClients() +void QMetaObjectPublisher::initializeClients() { if (!webChannel) { return; @@ -120,7 +225,7 @@ void QMetaObjectPublisherPrivate::initializeClients() { const QHash<QString, QObject *>::const_iterator end = registeredObjects.constEnd(); for (QHash<QString, QObject *>::const_iterator it = registeredObjects.constBegin(); it != end; ++it) { - const QJsonObject &info = q->classInfoForObject(it.value()); + const QJsonObject &info = classInfoForObject(it.value()); if (!propertyUpdatesInitialized) { initializePropertyUpdates(it.value(), info); } @@ -132,7 +237,7 @@ void QMetaObjectPublisherPrivate::initializeClients() pendingInit = false; } -void QMetaObjectPublisherPrivate::initializePropertyUpdates(const QObject *const object, const QJsonObject &objectInfo) +void QMetaObjectPublisher::initializePropertyUpdates(const QObject *const object, const QJsonObject &objectInfo) { foreach (const QJsonValue &propertyInfoVar, objectInfo[KEY_PROPERTIES].toArray()) { const QJsonArray &propertyInfo = propertyInfoVar.toArray(); @@ -164,7 +269,7 @@ void QMetaObjectPublisherPrivate::initializePropertyUpdates(const QObject *const signalHandler.connectTo(object, s_destroyedSignalIndex); } -void QMetaObjectPublisherPrivate::sendPendingPropertyUpdates() +void QMetaObjectPublisher::sendPendingPropertyUpdates() { if (blockUpdates || !clientIsIdle || pendingPropertyUpdates.isEmpty()) { return; @@ -204,7 +309,7 @@ void QMetaObjectPublisherPrivate::sendPendingPropertyUpdates() setClientIsIdle(false); } -bool QMetaObjectPublisherPrivate::invokeMethod(QObject *const object, const int methodIndex, +bool QMetaObjectPublisher::invokeMethod(QObject *const object, const int methodIndex, const QJsonArray &args, const QJsonValue &id) { const QMetaMethod &method = object->metaObject()->method(methodIndex); @@ -257,7 +362,7 @@ bool QMetaObjectPublisherPrivate::invokeMethod(QObject *const object, const int return true; } -void QMetaObjectPublisherPrivate::signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments) +void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments) { if (!webChannel) { return; @@ -280,12 +385,12 @@ void QMetaObjectPublisherPrivate::signalEmitted(const QObject *object, const int } else { pendingPropertyUpdates[object][signalIndex] = arguments; if (clientIsIdle && !blockUpdates && !timer.isActive()) { - timer.start(PROPERTY_UPDATE_INTERVAL, q); + timer.start(PROPERTY_UPDATE_INTERVAL, this); } } } -void QMetaObjectPublisherPrivate::objectDestroyed(const QObject *object) +void QMetaObjectPublisher::objectDestroyed(const QObject *object) { const QString &id = registeredObjectIds.take(object); Q_ASSERT(!id.isEmpty()); @@ -298,7 +403,7 @@ void QMetaObjectPublisherPrivate::objectDestroyed(const QObject *object) wrappedObjects.remove(object); } -QJsonValue QMetaObjectPublisherPrivate::wrapResult(const QVariant &result) +QJsonValue QMetaObjectPublisher::wrapResult(const QVariant &result) { if (QObject *object = result.value<QObject *>()) { QJsonObject &objectInfo = wrappedObjects[object]; @@ -314,7 +419,7 @@ QJsonValue QMetaObjectPublisherPrivate::wrapResult(const QVariant &result) objectInfo[KEY_QOBJECT] = true; objectInfo[KEY_ID] = id; - objectInfo[KEY_DATA] = q->classInfoForObject(object); + objectInfo[KEY_DATA] = classInfoForObject(object); registeredObjectIds[object] = id; registeredObjects[id] = object; @@ -328,7 +433,7 @@ QJsonValue QMetaObjectPublisherPrivate::wrapResult(const QVariant &result) return QJsonValue::fromVariant(result); } -void QMetaObjectPublisherPrivate::deleteWrappedObject(QObject *object) const +void QMetaObjectPublisher::deleteWrappedObject(QObject *object) const { if (!wrappedObjects.contains(object)) { qWarning() << "Not deleting non-wrapped object" << object; @@ -337,142 +442,6 @@ void QMetaObjectPublisherPrivate::deleteWrappedObject(QObject *object) const object->deleteLater(); } -QMetaObjectPublisher::QMetaObjectPublisher(QObject *parent) - : QObject(parent) - , d(new QMetaObjectPublisherPrivate(this)) -{ -} - -QMetaObjectPublisher::~QMetaObjectPublisher() -{ - -} - -QJsonObject QMetaObjectPublisher::classInfoForObjects(const QVariantMap &objectMap) const -{ - QJsonObject ret; - QMap<QString, QVariant>::const_iterator it = objectMap.constBegin(); - while (it != objectMap.constEnd()) { - QObject *object = it.value().value<QObject *>(); - if (object) { - const QJsonObject &info = classInfoForObject(object); - if (!info.isEmpty()) { - ret[it.key()] = info; - } - } - ++it; - } - return ret; -} - -QJsonObject QMetaObjectPublisher::classInfoForObject(QObject *object) const -{ - QJsonObject data; - if (!object) { - qWarning("null object given to MetaObjectPublisher - bad API usage?"); - return data; - } - - QJsonArray qtSignals; - QJsonArray qtMethods; - QJsonArray qtProperties; - QJsonObject qtEnums; - - const QMetaObject *metaObject = object->metaObject(); - QSet<int> notifySignals; - QSet<QString> identifiers; - for (int i = 0; i < metaObject->propertyCount(); ++i) { - const QMetaProperty &prop = metaObject->property(i); - QJsonArray propertyInfo; - const QString &propertyName = QString::fromLatin1(prop.name()); - propertyInfo.append(i); - propertyInfo.append(propertyName); - identifiers << propertyName; - QJsonArray signalInfo; - if (prop.hasNotifySignal()) { - notifySignals << prop.notifySignalIndex(); - const int numParams = prop.notifySignal().parameterCount(); - if (numParams > 1) { - qWarning("Notify signal for property '%s' has %d parameters, expected zero or one.", - prop.name(), numParams); - } - // optimize: compress the common propertyChanged notification names, just send a 1 - const QByteArray ¬ifySignal = prop.notifySignal().name(); - static const QByteArray changedSuffix = QByteArrayLiteral("Changed"); - if (notifySignal.length() == changedSuffix.length() + propertyName.length() && - notifySignal.endsWith(changedSuffix) && notifySignal.startsWith(prop.name())) - { - signalInfo.append(1); - } else { - signalInfo.append(QString::fromLatin1(notifySignal)); - } - signalInfo.append(prop.notifySignalIndex()); - } else if (!prop.isConstant()) { - qWarning("Property '%s'' of object '%s' has no notify signal and is not constant, " - "value updates in HTML will be broken!", - prop.name(), object->metaObject()->className()); - } - propertyInfo.append(signalInfo); - propertyInfo.append(QJsonValue::fromVariant(prop.read(object))); - qtProperties.append(propertyInfo); - } - for (int i = 0; i < metaObject->methodCount(); ++i) { - if (notifySignals.contains(i)) { - continue; - } - const QMetaMethod &method = metaObject->method(i); - //NOTE: this must be a string, otherwise it will be converted to '{}' in QML - const QString &name = QString::fromLatin1(method.name()); - // optimize: skip overloaded methods/signals or property getters, on the JS side we can only - // call one of them anyways - // TODO: basic support for overloaded signals, methods - if (identifiers.contains(name)) { - continue; - } - identifiers << name; - // send data as array to client with format: [name, index] - QJsonArray data; - data.append(name); - data.append(i); - if (method.methodType() == QMetaMethod::Signal) { - qtSignals.append(data); - } else if (method.access() == QMetaMethod::Public) { - qtMethods.append(data); - } - } - for (int i = 0; i < metaObject->enumeratorCount(); ++i) { - QMetaEnum enumerator = metaObject->enumerator(i); - QJsonObject values; - for (int k = 0; k < enumerator.keyCount(); ++k) { - values[QString::fromLatin1(enumerator.key(k))] = enumerator.value(k); - } - qtEnums[QString::fromLatin1(enumerator.name())] = values; - } - data[KEY_SIGNALS] = qtSignals; - data[KEY_METHODS] = qtMethods; - data[KEY_PROPERTIES] = qtProperties; - data[KEY_ENUMS] = qtEnums; - return data; -} - -void QMetaObjectPublisher::registerObjects(const QVariantMap &objects) -{ - if (d->propertyUpdatesInitialized) { - qWarning("Registered new object after initialization. This does not work!"); - return; - } - const QMap<QString, QVariant>::const_iterator end = objects.end(); - for (QMap<QString, QVariant>::const_iterator it = objects.begin(); it != end; ++it) { - QObject *object = it.value().value<QObject *>(); - if (!object) { - qWarning("Invalid QObject given to register under name %s", qPrintable(it.key())); - continue; - } - d->registeredObjects[it.key()] = object; - d->registeredObjectIds[object] = it.key(); - } -} - bool QMetaObjectPublisher::handleRequest(const QJsonObject &message) { if (!message.contains(KEY_DATA)) { @@ -486,13 +455,13 @@ bool QMetaObjectPublisher::handleRequest(const QJsonObject &message) const QString &type = payload.value(KEY_TYPE).toString(); if (type == TYPE_IDLE) { - d->setClientIsIdle(true); + setClientIsIdle(true); return true; } else if (type == TYPE_INIT) { - if (!d->blockUpdates) { - d->initializeClients(); + if (!blockUpdates) { + initializeClients(); } else { - d->pendingInit = true; + pendingInit = true; } return true; } else if (type == TYPE_DEBUG) { @@ -501,19 +470,19 @@ bool QMetaObjectPublisher::handleRequest(const QJsonObject &message) return true; } else if (payload.contains(KEY_OBJECT)) { const QString &objectName = payload.value(KEY_OBJECT).toString(); - QObject *object = d->registeredObjects.value(objectName); + QObject *object = registeredObjects.value(objectName); if (!object) { qWarning() << "Unknown object encountered" << objectName; return false; } if (type == TYPE_INVOKE_METHOD) { - return d->invokeMethod(object, payload.value(KEY_METHOD).toInt(-1), payload.value(KEY_ARGS).toArray(), message.value(KEY_ID)); + return invokeMethod(object, payload.value(KEY_METHOD).toInt(-1), payload.value(KEY_ARGS).toArray(), message.value(KEY_ID)); } else if (type == TYPE_CONNECT_TO_SIGNAL) { - d->signalHandler.connectTo(object, payload.value(KEY_SIGNAL).toInt(-1)); + signalHandler.connectTo(object, payload.value(KEY_SIGNAL).toInt(-1)); return true; } else if (type == TYPE_DISCONNECT_FROM_SIGNAL) { - d->signalHandler.disconnectFrom(object, payload.value(KEY_SIGNAL).toInt(-1)); + signalHandler.disconnectFrom(object, payload.value(KEY_SIGNAL).toInt(-1)); return true; } else if (type == TYPE_SET_PROPERTY) { const int propertyIdx = payload.value(KEY_PROPERTY).toInt(-1); @@ -534,65 +503,36 @@ bool QMetaObjectPublisher::handleRequest(const QJsonObject &message) void QMetaObjectPublisher::handleRawMessage(const QString &message) { - QJsonParseError error; - const QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8(), &error); - if (error.error) { - qWarning() << "Could not parse raw input message as JSON: " << error.errorString() << "Message was: " << message; - } else if (doc.isObject() && !handleRequest(doc.object())) { - qWarning() << "Could not handle raw message as meta object request: " << message; - } -} - -QWebChannel *QMetaObjectPublisher::webChannel() const -{ - return d->webChannel; -} - -void QMetaObjectPublisher::setWebChannel(QWebChannel *webChannel) -{ - if (d->webChannel == webChannel) { - return; + const QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8()); + if (doc.isObject()) { + handleRequest(doc.object()); } - - d->webChannel = webChannel; - - emit webChannelChanged(webChannel); -} - -bool QMetaObjectPublisher::blockUpdates() const -{ - return d->blockUpdates; } void QMetaObjectPublisher::setBlockUpdates(bool block) { - if (d->blockUpdates == block) { + if (blockUpdates == block) { return; } - d->blockUpdates = block; + blockUpdates = block; - if (!d->blockUpdates) { - if (d->pendingInit) { - d->initializeClients(); + if (!blockUpdates) { + if (pendingInit) { + initializeClients(); } else { - d->sendPendingPropertyUpdates(); + sendPendingPropertyUpdates(); } - } else if (d->timer.isActive()) { - d->timer.stop(); + } else if (timer.isActive()) { + timer.stop(); } emit blockUpdatesChanged(block); } -bool QMetaObjectPublisher::test_clientIsIdle() const -{ - return d->clientIsIdle; -} - void QMetaObjectPublisher::timerEvent(QTimerEvent *event) { - if (event->timerId() == d->timer.timerId()) { - d->sendPendingPropertyUpdates(); + if (event->timerId() == timer.timerId()) { + sendPendingPropertyUpdates(); } else { QObject::timerEvent(event); } diff --git a/src/webchannel/qmetaobjectpublisher.h b/src/webchannel/qmetaobjectpublisher.h deleted file mode 100644 index 9d17aff..0000000 --- a/src/webchannel/qmetaobjectpublisher.h +++ /dev/null @@ -1,121 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Copyright (C) 2013 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 QTMETAOBJECTPUBLISHER_H -#define QTMETAOBJECTPUBLISHER_H - -#include <QObject> -#include <QVariant> -#include <QJsonObject> - -#include "qwebchannelglobal.h" - -class QWebChannel; - -struct QMetaObjectPublisherPrivate; - -class Q_WEBCHANNEL_EXPORT QMetaObjectPublisher : public QObject -{ - Q_OBJECT - Q_PROPERTY(QWebChannel *webChannel READ webChannel WRITE setWebChannel NOTIFY webChannelChanged); - Q_PROPERTY(bool blockUpdates READ blockUpdates WRITE setBlockUpdates NOTIFY blockUpdatesChanged); - -public: - explicit QMetaObjectPublisher(QObject *parent = 0); - virtual ~QMetaObjectPublisher(); - - Q_INVOKABLE QJsonObject classInfoForObjects(const QVariantMap &objects) const; - Q_INVOKABLE QJsonObject classInfoForObject(QObject *object) const; - - /** - * Register a map of string ID to QObject* objects. - * - * The properties, signals and public methods of the QObject are - * published to the remote client, where an object with the given identifier - * is constructed. - * - * TODO: This must be called, before clients are initialized. - */ - Q_INVOKABLE void registerObjects(const QVariantMap &objects); - - /** - * Handle the given WebChannel client request and potentially give a response. - * - * @return true if the request was handled, false otherwise. - */ - Q_INVOKABLE bool handleRequest(const QJsonObject &message); - - QWebChannel *webChannel() const; - void setWebChannel(QWebChannel *webChannel); - - /** - * When updates are blocked, no property updates are transmitted to remote clients. - */ - bool blockUpdates() const; - void setBlockUpdates(bool block); - - /// TODO: cleanup: rewrite tests in C++ and access PIMPL data from there - Q_INVOKABLE bool test_clientIsIdle() const; - -signals: - void webChannelChanged(QWebChannel *channel); - void blockUpdatesChanged(bool block); - -public slots: - /** - * Helper slot which you can connect directly to WebChannel's rawMessageReceived signal. - * - * This slot then tries to parse the message as JSON and if it succeeds, calls handleRequest - * with the obtained JSON object. - */ - void handleRawMessage(const QString &message); - -protected: - void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE; - -private: - QScopedPointer<QMetaObjectPublisherPrivate> d; - friend struct QMetaObjectPublisherPrivate; - friend class TestWebChannel; -}; - -#endif // QMETAOBJECTPUBLISHER_H diff --git a/src/webchannel/qmetaobjectpublisher_p.h b/src/webchannel/qmetaobjectpublisher_p.h index 4df5b38..a7342d0 100644 --- a/src/webchannel/qmetaobjectpublisher_p.h +++ b/src/webchannel/qmetaobjectpublisher_p.h @@ -51,14 +51,40 @@ #include <QBasicTimer> #include <QPointer> -class QMetaObjectPublisher; class QWebChannel; #include "qwebchannelglobal.h" -struct Q_WEBCHANNEL_EXPORT QMetaObjectPublisherPrivate +class Q_WEBCHANNEL_EXPORT QMetaObjectPublisher : public QObject { - QMetaObjectPublisherPrivate(QMetaObjectPublisher *q); + Q_OBJECT + +public: + QMetaObjectPublisher(QWebChannel *webChannel); + virtual ~QMetaObjectPublisher(); + + /** + * Register @p object nuder the given @p id. + * + * The properties, signals and public methods of the QObject are + * published to the remote client, where an object with the given identifier + * is constructed. + * + * TODO: This must be called, before clients are initialized. + */ + void registerObject(const QString &id, QObject *object); + + /** + * Handle the given WebChannel client request and potentially give a response. + * + * @return true if the request was handled, false otherwise. + */ + bool handleRequest(const QJsonObject &message); + + /** + * Serialize the QMetaObject of @p object and return it in JSON form. + */ + QJsonObject classInfoForObject(const QObject *object) const; /** * Set the client to idle or busy, based on the value of @p isIdle. @@ -128,9 +154,33 @@ struct Q_WEBCHANNEL_EXPORT QMetaObjectPublisherPrivate */ void deleteWrappedObject(QObject *object) const; - QMetaObjectPublisher *q; - QPointer<QWebChannel> webChannel; - SignalHandler<QMetaObjectPublisherPrivate> signalHandler; + /** + * When updates are blocked, no property updates are transmitted to remote clients. + */ + void setBlockUpdates(bool block); + +public slots: + /** + * Helper slot which you can connect directly to WebChannel's rawMessageReceived signal. + * + * This slot then tries to parse the message as JSON and if it succeeds, calls handleRequest + * with the obtained JSON object. + */ + void handleRawMessage(const QString &message); + +signals: + void blockUpdatesChanged(bool block); + +protected: + void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE; + +private: + friend class QmlWebChannel; + friend class QWebChannel; + friend class TestWebChannel; + + QWebChannel *webChannel; + SignalHandler<QMetaObjectPublisher> signalHandler; // true when the client is idle, false otherwise bool clientIsIdle; diff --git a/src/webchannel/qwebchannel.cpp b/src/webchannel/qwebchannel.cpp index 8dc7e5e..026b31e 100644 --- a/src/webchannel/qwebchannel.cpp +++ b/src/webchannel/qwebchannel.cpp @@ -41,93 +41,14 @@ ****************************************************************************/ #include "qwebchannel.h" +#include "qwebchannel_p.h" +#include "qmetaobjectpublisher_p.h" +#include "qwebchannelsocket_p.h" -#include <QUuid> -#include <QStringList> -#include <QDebug> #include <QJsonDocument> #include <QJsonObject> -#include "qwebsocketserver_p.h" - -class QWebChannelPrivate : public QWebSocketServer -{ - Q_OBJECT -public: - QByteArray m_secret; - bool m_useSecret; - - QString m_baseUrl; - bool m_starting; - - QWebChannelPrivate(QObject* parent) - : QWebSocketServer(parent) - , m_useSecret(true) - , m_starting(false) - { - connect(this, SIGNAL(error(QAbstractSocket::SocketError)), - SLOT(socketError())); - } - - void initLater() - { - if (m_starting) - return; - metaObject()->invokeMethod(this, "init", Qt::QueuedConnection); - m_starting = true; - } - - void sendJSONMessage(const QJsonValue& id, const QJsonValue& data, bool response) const; - -signals: - void failed(const QString& reason); - void initialized(); - -protected: - bool isValid(const HeaderData& connection) Q_DECL_OVERRIDE; - -private slots: - void init(); - void socketError(); -}; - -bool QWebChannelPrivate::isValid(const HeaderData& connection) -{ - if (!QWebSocketServer::isValid(connection)) { - return false; - } - return connection.protocol == QByteArrayLiteral("QWebChannel") - && connection.path == m_secret; -} - -void QWebChannelPrivate::init() -{ - close(); - - m_starting = false; - if (m_useSecret) { - m_secret = QUuid::createUuid().toByteArray(); - // replace { by / - m_secret[0] = '/'; - // 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(port()).arg(QString::fromLatin1(m_secret)); - emit initialized(); -} - -void QWebChannelPrivate::socketError() -{ - emit failed(errorString()); -} - -void QWebChannelPrivate::sendJSONMessage(const QJsonValue& id, const QJsonValue& data, bool response) const +void QWebChannelPrivate::sendJSONMessage(const QJsonValue &id, const QJsonValue &data, bool response) const { QJsonObject obj; if (response) { @@ -138,22 +59,31 @@ void QWebChannelPrivate::sendJSONMessage(const QJsonValue& id, const QJsonValue& obj[QStringLiteral("data")] = data; } QJsonDocument doc(obj); - sendMessage(doc.toJson(QJsonDocument::Compact)); + socket->sendMessage(doc.toJson(QJsonDocument::Compact)); } QWebChannel::QWebChannel(QObject *parent) : QObject(parent) -, d(new QWebChannelPrivate(this)) +, d(new QWebChannelPrivate) { - connect(d, SIGNAL(textDataReceived(QString)), + d->socket = new QWebChannelSocket(this); + + connect(d->socket, SIGNAL(textDataReceived(QString)), SIGNAL(rawMessageReceived(QString))); - connect(d, SIGNAL(failed(QString)), + connect(d->socket, SIGNAL(failed(QString)), SIGNAL(failed(QString))); - connect(d, SIGNAL(initialized()), + connect(d->socket, SIGNAL(initialized()), SLOT(onInitialized())); - connect(d, SIGNAL(pongReceived()), + connect(d->socket, SIGNAL(pongReceived()), SIGNAL(pongReceived())); - d->initLater(); + + d->socket->initLater(); + + d->publisher = new QMetaObjectPublisher(this); + connect(d->publisher, SIGNAL(blockUpdatesChanged(bool)), + SIGNAL(blockUpdatesChanged(bool))); + connect(d->socket, SIGNAL(textDataReceived(QString)), + d->publisher, SLOT(handleRawMessage(QString))); } QWebChannel::~QWebChannel() @@ -162,26 +92,49 @@ QWebChannel::~QWebChannel() QString QWebChannel::baseUrl() const { - return d->m_baseUrl; + return d->socket->m_baseUrl; } void QWebChannel::setUseSecret(bool s) { - if (d->m_useSecret == s) + if (d->socket->m_useSecret == s) return; - d->m_useSecret = s; - d->initLater(); + d->socket->m_useSecret = s; + d->socket->initLater(); } bool QWebChannel::useSecret() const { - return d->m_useSecret; + return d->socket->m_useSecret; +} + +void QWebChannel::registerObjects(const QHash< QString, QObject * > &objects) +{ + const QHash<QString, QObject *>::const_iterator end = objects.constEnd(); + for (QHash<QString, QObject *>::const_iterator it = objects.constBegin(); it != end; ++it) { + d->publisher->registerObject(it.key(), it.value()); + } +} + +void QWebChannel::registerObject(const QString &id, QObject *object) +{ + d->publisher->registerObject(id, object); +} + +bool QWebChannel::blockUpdates() const +{ + return d->publisher->blockUpdates; +} + +void QWebChannel::setBlockUpdates(bool block) +{ + d->publisher->setBlockUpdates(block); } void QWebChannel::onInitialized() { emit initialized(); - emit baseUrlChanged(d->m_baseUrl); + emit baseUrlChanged(d->socket->m_baseUrl); } void QWebChannel::respond(const QJsonValue& messageId, const QJsonValue& data) const @@ -196,12 +149,12 @@ void QWebChannel::sendMessage(const QJsonValue& id, const QJsonValue& data) cons void QWebChannel::sendRawMessage(const QString& message) const { - d->sendMessage(message.toUtf8()); + d->socket->sendMessage(message.toUtf8()); } void QWebChannel::ping() const { - d->ping(); + d->socket->ping(); } #include "qwebchannel.moc" diff --git a/src/webchannel/qwebchannel.h b/src/webchannel/qwebchannel.h index 321706e..ec62208 100644 --- a/src/webchannel/qwebchannel.h +++ b/src/webchannel/qwebchannel.h @@ -56,16 +56,43 @@ class Q_WEBCHANNEL_EXPORT QWebChannel : public QObject Q_DISABLE_COPY(QWebChannel) Q_PROPERTY(QString baseUrl READ baseUrl NOTIFY baseUrlChanged) Q_PROPERTY(bool useSecret READ useSecret WRITE setUseSecret) + Q_PROPERTY(bool blockUpdates READ blockUpdates WRITE setBlockUpdates NOTIFY blockUpdatesChanged); public: QWebChannel(QObject *parent = 0); ~QWebChannel(); - QString baseUrl() const; + QString baseUrl() const; void setUseSecret(bool); bool useSecret() const; + /** + * Register a map of string ID to QObject* objects. + * + * The properties, signals and public methods of the QObject are + * published to the remote client, where an object with the given identifier + * is constructed. + * + * TODO: This must be called, before clients are initialized. + */ + void registerObjects(const QHash<QString, QObject*> &objects); + void registerObject(const QString &id, QObject *object); + + /** + * @return true when property updates are blocked, false otherwise. + */ + bool blockUpdates() const; + + /** + * Set whether property updates should be blocked or not. + * + * When they are blocked, the remote clients will not be notified about + * property changes. The changes are recorded and sent to the clients once + * setBlockUpdates(false) is called. + */ + void setBlockUpdates(bool block); + signals: void baseUrlChanged(const QString& baseUrl); void rawMessageReceived(const QString& rawMessage); @@ -74,6 +101,8 @@ signals: void failed(const QString& reason); + void blockUpdatesChanged(bool block); + public slots: void sendMessage(const QJsonValue& id, const QJsonValue& data = QJsonValue()) const; void respond(const QJsonValue& messageId, const QJsonValue& data = QJsonValue()) const; @@ -84,7 +113,9 @@ private slots: void onInitialized(); private: - QWebChannelPrivate* d; + QScopedPointer<QWebChannelPrivate> d; + friend class QmlWebChannel; + friend class TestWebChannel; }; #endif // QWEBCHANNEL_H diff --git a/src/webchannel/qwebchannel_p.h b/src/webchannel/qwebchannel_p.h new file mode 100644 index 0000000..3b8a34b --- /dev/null +++ b/src/webchannel/qwebchannel_p.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 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 QWEBCHANNEL_P_H +#define QWEBCHANNEL_P_H + +#include "qwebchannelglobal.h" + +class QJsonValue; +class QWebChannelSocket; +class QMetaObjectPublisher; + +struct Q_WEBCHANNEL_EXPORT QWebChannelPrivate +{ + QWebChannelSocket *socket; + QMetaObjectPublisher *publisher; + + void sendJSONMessage(const QJsonValue &id, const QJsonValue &data, bool response) const; +}; + +#endif // QWEBCHANNEL_P_H diff --git a/src/webchannel/qwebchannelsocket.cpp b/src/webchannel/qwebchannelsocket.cpp new file mode 100644 index 0000000..c5275d0 --- /dev/null +++ b/src/webchannel/qwebchannelsocket.cpp @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 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 "qwebchannelsocket_p.h" + +#include <QUuid> +#include <QDebug> + +QWebChannelSocket::QWebChannelSocket(QObject *parent) + : QWebSocketServer(parent) + , m_useSecret(true) + , m_starting(false) +{ + connect(this, SIGNAL(error(QAbstractSocket::SocketError)), + SLOT(socketError())); +} + +void QWebChannelSocket::initLater() +{ + if (m_starting) + return; + metaObject()->invokeMethod(this, "init", Qt::QueuedConnection); + m_starting = true; +} + +bool QWebChannelSocket::isValid(const HeaderData &connection) +{ + if (!QWebSocketServer::isValid(connection)) { + return false; + } + return connection.protocol == QByteArrayLiteral("QWebChannel") + && connection.path == m_secret; +} + +void QWebChannelSocket::init() +{ + close(); + + m_starting = false; + if (m_useSecret) { + m_secret = QUuid::createUuid().toByteArray(); + // replace { by / + m_secret[0] = '/'; + // 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(port()).arg(QString::fromLatin1(m_secret)); + emit initialized(); +} + +void QWebChannelSocket::socketError() +{ + emit failed(errorString()); +} diff --git a/src/webchannel/qwebchannelsocket_p.h b/src/webchannel/qwebchannelsocket_p.h new file mode 100644 index 0000000..234c200 --- /dev/null +++ b/src/webchannel/qwebchannelsocket_p.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 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 QWEBCHANNELSOCKET_P_H +#define QWEBCHANNELSOCKET_P_H + +#include "qwebsocketserver_p.h" + +class QWebChannelSocket : public QWebSocketServer +{ + Q_OBJECT +public: + QByteArray m_secret; + QString m_baseUrl; + + bool m_useSecret; + bool m_starting; + + QWebChannelSocket(QObject *parent); + + void initLater(); + +signals: + void failed(const QString &reason); + void initialized(); + +protected: + bool isValid(const HeaderData &connection) Q_DECL_OVERRIDE; + +private slots: + void init(); + void socketError(); +}; + +#endif // QWEBCHANNELSOCKET_P_H diff --git a/src/webchannel/signalhandler_p.h b/src/webchannel/signalhandler_p.h index 66bdb28..8c49346 100644 --- a/src/webchannel/signalhandler_p.h +++ b/src/webchannel/signalhandler_p.h @@ -141,7 +141,7 @@ SignalHandler<Receiver>::SignalHandler(Receiver *receiver, QObject *parent) * * The return value is also verified to ensure it is a signal. */ -QMetaMethod findSignal(const QMetaObject *metaObject, const int signalIndex) +inline QMetaMethod findSignal(const QMetaObject *metaObject, const int signalIndex) { QMetaMethod signal = metaObject->method(signalIndex); if (!signal.isValid()) { diff --git a/src/webchannel/webchannel.pro b/src/webchannel/webchannel.pro index 376711d..0678346 100644 --- a/src/webchannel/webchannel.pro +++ b/src/webchannel/webchannel.pro @@ -11,17 +11,20 @@ OTHER_FILES = \ qwebchannel.js PUBLIC_HEADERS += \ - qwebchannel.h \ - qmetaobjectpublisher.h + qwebchannel.h PRIVATE_HEADERS += \ + qwebchannel_p.h \ + qmetaobjectpublisher_p.h \ qwebsocketserver_p.h \ + qwebchannelsocket_p.h \ variantargument_p.h \ signalhandler_p.h SOURCES += \ qwebchannel.cpp \ qmetaobjectpublisher.cpp \ - qwebsocketserver.cpp + qwebsocketserver.cpp \ + qwebchannelsocket.cpp HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS diff --git a/tests/qml/tst_bench.qml b/tests/qml/tst_bench.qml index 46eb6be..c6359ce 100644 --- a/tests/qml/tst_bench.qml +++ b/tests/qml/tst_bench.qml @@ -75,18 +75,6 @@ WebChannelTest { } } - MetaObjectPublisher { - id: publisher - webChannel: test.webChannel - } - - Connections { - target: test.webChannel - onRawMessageReceived: { - publisher.handleRequest(JSON.parse(rawMessage)); - } - } - property var objects: ({}) function initTestCase() @@ -97,7 +85,7 @@ WebChannelTest { objects[id] = component.createObject(test, properties); } - publisher.registerObjects(objects); + webChannel.registerObjects(objects); } function benchmark_init_baseline() diff --git a/tests/qml/tst_metaobjectpublisher.qml b/tests/qml/tst_metaobjectpublisher.qml index 5e1e825..cc679ad 100644 --- a/tests/qml/tst_metaobjectpublisher.qml +++ b/tests/qml/tst_metaobjectpublisher.qml @@ -78,26 +78,9 @@ WebChannelTest { } } - MetaObjectPublisher { - id: publisher - webChannel: test.webChannel - } - - Connections { - target: test.webChannel - onRawMessageReceived: { - var message = JSON.parse(rawMessage); - verify(message); - var handled = publisher.handleRequest(message); - if (message.data && message.data.type) { - verify(handled); - } - } - } - function initTestCase() { - publisher.registerObjects({ + webChannel.registerObjects({ "myObj": myObj, "myOtherObj": myOtherObj, "myFactory": myFactory @@ -119,7 +102,7 @@ WebChannelTest { verify(msg); verify(msg.data); compare(msg.data.type, "Qt.idle"); - verify(publisher.test_clientIsIdle()) + verify(webChannel.test_clientIsIdle()) } function awaitMessageSkipIdle() diff --git a/tests/webchannel/tst_webchannel.cpp b/tests/webchannel/tst_webchannel.cpp index 3b90744..7fbe5f9 100644 --- a/tests/webchannel/tst_webchannel.cpp +++ b/tests/webchannel/tst_webchannel.cpp @@ -43,7 +43,7 @@ #include "tst_webchannel.h" #include <qwebchannel.h> -#include <qmetaobjectpublisher.h> +#include <qwebchannel_p.h> #include <qmetaobjectpublisher_p.h> #include <QtTest> @@ -58,7 +58,7 @@ TestWebChannel::~TestWebChannel() } - void TestWebChannel::testInitChannel() +void TestWebChannel::testInitChannel() { QWebChannel channel; @@ -76,26 +76,24 @@ TestWebChannel::~TestWebChannel() void TestWebChannel::testRegisterObjects() { QWebChannel channel; - QMetaObjectPublisher publisher; - publisher.setWebChannel(&channel); - QObject plain; - QVariantMap objects; - objects["plain"] = QVariant::fromValue(&plain); - objects["channel"] = QVariant::fromValue(&channel); - objects["publisher"] = QVariant::fromValue(&publisher); - objects["test"] = QVariant::fromValue(this); + QHash<QString, QObject*> objects; + objects[QStringLiteral("plain")] = &plain; + objects[QStringLiteral("channel")] = &channel; + objects[QStringLiteral("publisher")] = channel.d->publisher; + objects[QStringLiteral("test")] = this; - publisher.registerObjects(objects); + channel.registerObjects(objects); } void TestWebChannel::testInfoForObject() { TestObject obj; obj.setObjectName("myTestObject"); - QMetaObjectPublisher publisher; - const QJsonObject info = publisher.classInfoForObject(&obj); + + QWebChannel channel; + const QJsonObject info = channel.d->publisher->classInfoForObject(&obj); QCOMPARE(info.keys(), QStringList() << "enums" << "methods" << "properties" << "signals"); @@ -216,12 +214,13 @@ void TestWebChannel::testInfoForObject() } } -static QVariantMap createObjects(QObject *parent) +static QHash<QString, QObject*> createObjects(QObject *parent) { const int num = 100; - QVariantMap objects; + QHash<QString, QObject*> objects; + objects.reserve(num); for (int i = 0; i < num; ++i) { - objects[QStringLiteral("obj%1").arg(i)] = QVariant::fromValue(new BenchObject(parent)); + objects[QStringLiteral("obj%1").arg(i)] = new BenchObject(parent); } return objects; } @@ -232,14 +231,13 @@ void TestWebChannel::benchClassInfo() QSignalSpy initSpy(&channel, SIGNAL(initialized())); QVERIFY(initSpy.wait()); - QMetaObjectPublisher publisher; - publisher.setWebChannel(&channel); - QObject parent; - const QVariantMap objects = createObjects(&parent); + const QHash<QString, QObject*> objects = createObjects(&parent); QBENCHMARK { - publisher.classInfoForObjects(objects); + foreach (const QObject *object, objects) { + channel.d->publisher->classInfoForObject(object); + } } } @@ -249,19 +247,16 @@ void TestWebChannel::benchInitializeClients() QSignalSpy initSpy(&channel, SIGNAL(initialized())); QVERIFY(initSpy.wait()); - QMetaObjectPublisher publisher; - publisher.setWebChannel(&channel); - QObject parent; - const QVariantMap objects = createObjects(&parent); - publisher.registerObjects(objects); + channel.registerObjects(createObjects(&parent)); + QMetaObjectPublisher *publisher = channel.d->publisher; QBENCHMARK { - publisher.d->initializeClients(); + publisher->initializeClients(); - publisher.d->propertyUpdatesInitialized = false; - publisher.d->signalToPropertyMap.clear(); - publisher.d->signalHandler.clear(); + publisher->propertyUpdatesInitialized = false; + publisher->signalToPropertyMap.clear(); + publisher->signalHandler.clear(); } } @@ -271,26 +266,24 @@ void TestWebChannel::benchPropertyUpdates() QSignalSpy initSpy(&channel, SIGNAL(initialized())); QVERIFY(initSpy.wait()); - QMetaObjectPublisher publisher; - publisher.setWebChannel(&channel); - QObject parent; - const QVariantMap objects = createObjects(&parent); + const QHash<QString, QObject*> objects = createObjects(&parent); QVector<BenchObject*> objectList; - foreach (const QVariant &var, objects) { - objectList << var.value<BenchObject*>(); + objectList.reserve(objects.size()); + foreach (QObject *obj, objects) { + objectList << qobject_cast<BenchObject*>(obj); } - publisher.registerObjects(objects); - publisher.d->initializeClients(); + channel.registerObjects(objects); + channel.d->publisher->initializeClients(); QBENCHMARK { foreach (BenchObject *obj, objectList) { obj->change(); } - publisher.d->clientIsIdle = true; - publisher.d->sendPendingPropertyUpdates(); + channel.d->publisher->clientIsIdle = true; + channel.d->publisher->sendPendingPropertyUpdates(); } } @@ -300,14 +293,11 @@ void TestWebChannel::benchRegisterObjects() QSignalSpy initSpy(&channel, SIGNAL(initialized())); QVERIFY(initSpy.wait()); - QMetaObjectPublisher publisher; - publisher.setWebChannel(&channel); - QObject parent; - const QVariantMap objects = createObjects(&parent); + const QHash<QString, QObject*> objects = createObjects(&parent); QBENCHMARK { - publisher.registerObjects(objects); + channel.registerObjects(objects); } } |