diff options
Diffstat (limited to 'src/webchannel')
-rw-r--r-- | src/webchannel/qmetaobjectpublisher.cpp | 175 | ||||
-rw-r--r-- | src/webchannel/qmetaobjectpublisher_p.h | 40 | ||||
-rw-r--r-- | src/webchannel/qwebchannel.js | 29 |
3 files changed, 149 insertions, 95 deletions
diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp index b92cf51..0c22798 100644 --- a/src/webchannel/qmetaobjectpublisher.cpp +++ b/src/webchannel/qmetaobjectpublisher.cpp @@ -41,6 +41,7 @@ #include <QDebug> #include <QJsonObject> #include <QJsonArray> +#include <QJSValue> #include <QUuid> QT_BEGIN_NAMESPACE @@ -73,6 +74,15 @@ const QString KEY_ARGS = QStringLiteral("args"); const QString KEY_PROPERTY = QStringLiteral("property"); const QString KEY_VALUE = QStringLiteral("value"); +QJsonObject createResponse(const QJsonValue &id, const QJsonValue &data) +{ + QJsonObject response; + response[KEY_TYPE] = TypeResponse; + response[KEY_ID] = id; + response[KEY_DATA] = data; + return response; +} + /// TODO: what is the proper value here? const int PROPERTY_UPDATE_INTERVAL = 50; } @@ -83,7 +93,6 @@ QMetaObjectPublisher::QMetaObjectPublisher(QWebChannel *webChannel) , signalHandler(this) , clientIsIdle(false) , blockUpdates(false) - , pendingInit(false) , propertyUpdatesInitialized(false) { } @@ -102,11 +111,11 @@ void QMetaObjectPublisher::registerObject(const QString &id, QObject *object) qWarning("Registered new object after initialization, existing clients won't be notified!"); // TODO: send a message to clients that an object was added } - initializePropertyUpdates(object, classInfoForObject(object)); + initializePropertyUpdates(object, classInfoForObject(object, Q_NULLPTR)); } } -QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object) +QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object, QWebChannelAbstractTransport *transport) { QJsonObject data; if (!object) { @@ -154,7 +163,7 @@ QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object) prop.name(), object->metaObject()->className()); } propertyInfo.append(signalInfo); - propertyInfo.append(wrapResult(prop.read(object))); + propertyInfo.append(wrapResult(prop.read(object), transport)); qtProperties.append(propertyInfo); } for (int i = 0; i < metaObject->methodCount(); ++i) { @@ -211,29 +220,21 @@ void QMetaObjectPublisher::setClientIsIdle(bool isIdle) } } -void QMetaObjectPublisher::initializeClients() +QJsonObject QMetaObjectPublisher::initializeClient(QWebChannelAbstractTransport *transport) { - if (!webChannel) { - return; - } - QJsonObject objectInfos; { const QHash<QString, QObject *>::const_iterator end = registeredObjects.constEnd(); for (QHash<QString, QObject *>::const_iterator it = registeredObjects.constBegin(); it != end; ++it) { - const QJsonObject &info = classInfoForObject(it.value()); + const QJsonObject &info = classInfoForObject(it.value(), transport); if (!propertyUpdatesInitialized) { initializePropertyUpdates(it.value(), info); } objectInfos[it.key()] = info; } } - QJsonObject message; - message[KEY_TYPE] = TypeInit; - message[KEY_DATA] = objectInfos; - broadcastMessage(message); propertyUpdatesInitialized = true; - pendingInit = false; + return objectInfos; } void QMetaObjectPublisher::initializePropertyUpdates(const QObject *const object, const QJsonObject &objectInfo) @@ -275,12 +276,14 @@ void QMetaObjectPublisher::sendPendingPropertyUpdates() } QJsonArray data; + QHash<QWebChannelAbstractTransport*, QJsonArray> specificUpdates; // convert pending property updates to JSON data const PendingPropertyUpdates::const_iterator end = pendingPropertyUpdates.constEnd(); for (PendingPropertyUpdates::const_iterator it = pendingPropertyUpdates.constBegin(); it != end; ++it) { const QObject *object = it.key(); const QMetaObject *const metaObject = object->metaObject(); + const QString objectId = registeredObjectIds.value(object); const SignalToPropertyNameMap &objectsSignalToPropertyMap = signalToPropertyMap.value(object); // maps property name to current property value QJsonObject properties; @@ -292,26 +295,47 @@ void QMetaObjectPublisher::sendPendingPropertyUpdates() foreach (const int propertyIndex, objectsSignalToPropertyMap.value(sigIt.key())) { const QMetaProperty &property = metaObject->property(propertyIndex); Q_ASSERT(property.isValid()); - properties[QString::number(propertyIndex)] = wrapResult(property.read(object)); + properties[QString::number(propertyIndex)] = wrapResult(property.read(object), Q_NULLPTR, objectId); } sigs[QString::number(sigIt.key())] = QJsonArray::fromVariantList(sigIt.value()); } QJsonObject obj; - obj[KEY_OBJECT] = registeredObjectIds.value(object); + obj[KEY_OBJECT] = objectId; obj[KEY_SIGNALS] = sigs; obj[KEY_PROPERTIES] = properties; - data.push_back(obj); + + // if the object is auto registered, just send the update only to clients which know this object + if (wrappedObjects.contains(objectId)) { + foreach (QWebChannelAbstractTransport *transport, wrappedObjects.value(objectId).transports) { + QJsonArray &arr = specificUpdates[transport]; + arr.push_back(obj); + } + } else { + data.push_back(obj); + } } pendingPropertyUpdates.clear(); QJsonObject message; message[KEY_TYPE] = TypePropertyUpdate; - message[KEY_DATA] = data; - setClientIsIdle(false); - broadcastMessage(message); + + // data does not contain specific updates + if (!data.isEmpty()) { + setClientIsIdle(false); + + message[KEY_DATA] = data; + broadcastMessage(message); + } + + // send every property update which is not supposed to be broadcasted + const QHash<QWebChannelAbstractTransport*, QJsonArray>::const_iterator suend = specificUpdates.constEnd(); + for (QHash<QWebChannelAbstractTransport*, QJsonArray>::const_iterator it = specificUpdates.constBegin(); it != suend; ++it) { + message[KEY_DATA] = it.value(); + it.key()->sendMessage(message); + } } -QJsonValue QMetaObjectPublisher::invokeMethod(QObject *const object, const int methodIndex, +QVariant QMetaObjectPublisher::invokeMethod(QObject *const object, const int methodIndex, const QJsonArray &args) { const QMetaMethod &method = object->metaObject()->method(methodIndex); @@ -362,7 +386,7 @@ QJsonValue QMetaObjectPublisher::invokeMethod(QObject *const object, const int m arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9]); - return wrapResult(returnValue); + return returnValue; } void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments) @@ -379,10 +403,18 @@ void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signal message[KEY_OBJECT] = objectName; message[KEY_SIGNAL] = signalIndex; if (!arguments.isEmpty()) { - message[KEY_ARGS] = wrapList(arguments); + message[KEY_ARGS] = wrapList(arguments, Q_NULLPTR, objectName); } message[KEY_TYPE] = TypeSignal; - broadcastMessage(message); + + // if the object is wrapped, just send the response to clients which know this object + if (wrappedObjects.contains(objectName)) { + foreach (QWebChannelAbstractTransport *transport, wrappedObjects.value(objectName).transports) { + transport->sendMessage(message); + } + } else { + broadcastMessage(message); + } if (signalIndex == s_destroyedSignalIndex) { objectDestroyed(object); @@ -399,65 +431,87 @@ void QMetaObjectPublisher::objectDestroyed(const QObject *object) { const QString &id = registeredObjectIds.take(object); Q_ASSERT(!id.isEmpty()); - bool removed = registeredObjects.remove(id); + bool removed = registeredObjects.remove(id) + || wrappedObjects.remove(id); Q_ASSERT(removed); Q_UNUSED(removed); signalHandler.remove(object); signalToPropertyMap.remove(object); pendingPropertyUpdates.remove(object); - wrappedObjects.remove(object); } -QJsonValue QMetaObjectPublisher::wrapResult(const QVariant &result) +// NOTE: transport can be a nullptr +// in such a case, we need to ensure that the property is registered to +// the target transports of the parentObjectId +QJsonValue QMetaObjectPublisher::wrapResult(const QVariant &result, QWebChannelAbstractTransport *transport, + const QString &parentObjectId) { if (QObject *object = result.value<QObject *>()) { - QJsonObject objectInfo; - objectInfo[KEY_QOBJECT] = true; 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)); + + classInfo = classInfoForObject(object, transport); registeredObjectIds[object] = id; - registeredObjects[id] = object; - - QJsonObject info = classInfoForObject(object); - wrappedObjects[object] = info; - objectInfo[KEY_DATA] = info; - if (propertyUpdatesInitialized) { - // if other objects are initialized already, do the same here - initializePropertyUpdates(object, info); + + ObjectInfo oi(object, classInfo); + if (transport) { + oi.transports.append(transport); + } else { + // use the transports from the parent object + oi.transports = wrappedObjects.value(parentObjectId).transports; + // or fallback to all transports if the parent is not wrapped + if (oi.transports.isEmpty()) + oi.transports = webChannel->d_func()->transports; } - } else if (wrappedObjects.contains(object)) { - // if this object was wrapped, send the full class info - // this is required for proper multi-client support - objectInfo[KEY_DATA] = wrappedObjects.value(object); + wrappedObjects.insert(id, oi); + + initializePropertyUpdates(object, classInfo); + } else if (wrappedObjects.contains(id)) { + Q_ASSERT(object == wrappedObjects.value(id).object); + // check if this transport is already assigned to the object + if (transport && !wrappedObjects.value(id).transports.contains(transport)) + wrappedObjects[id].transports.append(transport); + classInfo = wrappedObjects.value(id).classinfo; } + QJsonObject objectInfo; + objectInfo[KEY_QOBJECT] = true; objectInfo[KEY_ID] = id; + if (!classInfo.isEmpty()) + objectInfo[KEY_DATA] = classInfo; return objectInfo; + } 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); } else if (result.canConvert<QVariantList>()) { // recurse and potentially wrap contents of the array - return wrapList(result.toList()); + return wrapList(result.toList(), transport); } - // no need to wrap this return QJsonValue::fromVariant(result); } -QJsonArray QMetaObjectPublisher::wrapList(const QVariantList &list) +QJsonArray QMetaObjectPublisher::wrapList(const QVariantList &list, QWebChannelAbstractTransport *transport, const QString &parentObjectId) { QJsonArray array; foreach (const QVariant &arg, list) { - array.append(wrapResult(arg)); + array.append(wrapResult(arg, transport, parentObjectId)); } return array; } void QMetaObjectPublisher::deleteWrappedObject(QObject *object) const { - if (!wrappedObjects.contains(object)) { + if (!wrappedObjects.contains(registeredObjectIds.value(object))) { qWarning() << "Not deleting non-wrapped object" << object; return; } @@ -492,17 +546,21 @@ void QMetaObjectPublisher::handleMessage(const QJsonObject &message, QWebChannel if (type == TypeIdle) { setClientIsIdle(true); } else if (type == TypeInit) { - if (!blockUpdates) { - initializeClients(); - } else { - pendingInit = true; + if (!message.contains(KEY_ID)) { + qWarning("JSON message object is missing the id property: %s", + QJsonDocument(message).toJson().constData()); + return; } + transport->sendMessage(createResponse(message.value(KEY_ID), initializeClient(transport))); } else if (type == TypeDebug) { static QTextStream out(stdout); out << "DEBUG: " << message.value(KEY_DATA).toString() << endl; } else if (message.contains(KEY_OBJECT)) { const QString &objectName = message.value(KEY_OBJECT).toString(); QObject *object = registeredObjects.value(objectName); + if (!object) + object = wrappedObjects.value(objectName).object; + if (!object) { qWarning() << "Unknown object encountered" << objectName; return; @@ -514,11 +572,10 @@ void QMetaObjectPublisher::handleMessage(const QJsonObject &message, QWebChannel QJsonDocument(message).toJson().constData()); return; } - QJsonObject response; - response[KEY_TYPE] = TypeResponse; - response[KEY_ID] = message.value(KEY_ID); - response[KEY_DATA] = invokeMethod(object, message.value(KEY_METHOD).toInt(-1), message.value(KEY_ARGS).toArray()); - transport->sendMessage(response); + + transport->sendMessage(createResponse(message.value(KEY_ID), + wrapResult(invokeMethod(object, message.value(KEY_METHOD).toInt(-1), + message.value(KEY_ARGS).toArray()), transport))); } else if (type == TypeConnectToSignal) { signalHandler.connectTo(object, message.value(KEY_SIGNAL).toInt(-1)); } else if (type == TypeDisconnectFromSignal) { @@ -544,11 +601,7 @@ void QMetaObjectPublisher::setBlockUpdates(bool block) blockUpdates = block; if (!blockUpdates) { - if (pendingInit) { - initializeClients(); - } else { - sendPendingPropertyUpdates(); - } + sendPendingPropertyUpdates(); } else if (timer.isActive()) { timer.stop(); } diff --git a/src/webchannel/qmetaobjectpublisher_p.h b/src/webchannel/qmetaobjectpublisher_p.h index 05f33bd..359135c 100644 --- a/src/webchannel/qmetaobjectpublisher_p.h +++ b/src/webchannel/qmetaobjectpublisher_p.h @@ -40,6 +40,8 @@ #include <QStringList> #include <QMetaObject> #include <QBasicTimer> +#include <QPointer> +#include <QJsonObject> #include "qwebchannelglobal.h" @@ -70,7 +72,6 @@ class QWebChannelAbstractTransport; class Q_WEBCHANNEL_EXPORT QMetaObjectPublisher : public QObject { Q_OBJECT - public: explicit QMetaObjectPublisher(QWebChannel *webChannel); virtual ~QMetaObjectPublisher(); @@ -94,7 +95,7 @@ public: /** * Serialize the QMetaObject of @p object and return it in JSON form. */ - QJsonObject classInfoForObject(const QObject *object); + QJsonObject classInfoForObject(const QObject *object, QWebChannelAbstractTransport *transport); /** * Set the client to idle or busy, based on the value of @p isIdle. @@ -108,7 +109,7 @@ public: * * Furthermore, if that was not done already, connect to their property notify signals. */ - void initializeClients(); + QJsonObject initializeClient(QWebChannelAbstractTransport *transport); /** * Go through all properties of the given object and connect to their notify signal. @@ -135,7 +136,7 @@ public: * The return value of the method invocation is then serialized and a response message * is returned. */ - QJsonValue invokeMethod(QObject *const object, const int methodIndex, const QJsonArray &args); + QVariant invokeMethod(QObject *const object, const int methodIndex, const QJsonArray &args); /** * Callback of the signalHandler which forwards the signal invocation to the webchannel clients. @@ -155,14 +156,16 @@ public: * * All other input types are returned as-is. */ - QJsonValue wrapResult(const QVariant &result); + QJsonValue wrapResult(const QVariant &result, QWebChannelAbstractTransport *transport, + const QString &parentObjectId = QString()); /** * Convert a list of variant values for consumption by the client. * * This properly handles QML values and also wraps the result if required. */ - QJsonArray wrapList(const QVariantList &list); + QJsonArray wrapList(const QVariantList &list, QWebChannelAbstractTransport *transport, + const QString &parentObjectId = QString()); /** * Invoke delete later on @p object. @@ -200,10 +203,6 @@ private: // true when no property updates should be sent, false otherwise bool blockUpdates; - // true when at least one client needs to be initialized, - // i.e. when a Qt.init came in which was not handled yet. - bool pendingInit; - // true when at least one client was initialized and thus // the property updates have been initialized and the // object info map set. @@ -215,6 +214,24 @@ private: // Map the registered objects to their id. QHash<const QObject *, QString> registeredObjectIds; + // Groups individually wrapped objects with their class information and the transports that have access to it. + struct ObjectInfo + { + ObjectInfo() + : object(Q_NULLPTR) + {} + ObjectInfo(QObject *o, const QJsonObject &i) + : object(o) + , classinfo(i) + {} + QObject *object; + QJsonObject classinfo; + QVector<QWebChannelAbstractTransport*> transports; + }; + + // Map of objects wrapped from invocation returns + QHash<QString, ObjectInfo> wrappedObjects; + // Map of objects to maps of signal indices to a set of all their property indices. // The last value is a set as a signal can be the notify signal of multiple properties. typedef QHash<int, QSet<int> > SignalToPropertyNameMap; @@ -226,9 +243,6 @@ private: typedef QHash<const QObject *, SignalToArgumentsMap> PendingPropertyUpdates; PendingPropertyUpdates pendingPropertyUpdates; - // Maps wrapped object to class info - QHash<const QObject *, QJsonObject> wrappedObjects; - // Aggregate property updates since we get multiple Qt.idle message when we have multiple // clients. They all share the same QWebProcess though so we must take special care to // prevent message flooding. diff --git a/src/webchannel/qwebchannel.js b/src/webchannel/qwebchannel.js index 472330e..4a1c784 100644 --- a/src/webchannel/qwebchannel.js +++ b/src/webchannel/qwebchannel.js @@ -82,9 +82,6 @@ var QWebChannel = function(transport, initCallback) case QWebChannelMessageTypes.propertyUpdate: channel.handlePropertyUpdate(data); break; - case QWebChannelMessageTypes.init: - channel.handleInit(data); - break; default: console.error("invalid message received:", message.data); break; @@ -149,17 +146,14 @@ var QWebChannel = function(transport, initCallback) channel.exec({type: QWebChannelMessageTypes.idle}); } - // prevent multiple initialization which might happen with multiple webchannel clients. - this.initialized = false; - this.handleInit = function(message) + this.debug = function(message) { - if (channel.initialized) { - return; - } - channel.initialized = true; - for (var objectName in message.data) { - var data = message.data[objectName]; - var object = new QObject(objectName, data, channel); + channel.send({type: QWebChannelMessageTypes.debug, data: message}); + }; + + channel.exec({type: QWebChannelMessageTypes.init}, function(data) { + for (var objectName in data) { + var object = new QObject(objectName, data[objectName], channel); } // now unwrap properties, which might reference other registered objects for (var objectName in channel.objects) { @@ -169,14 +163,7 @@ var QWebChannel = function(transport, initCallback) initCallback(channel); } channel.exec({type: QWebChannelMessageTypes.idle}); - } - - this.debug = function(message) - { - channel.send({type: QWebChannelMessageTypes.debug, data: message}); - }; - - channel.exec({type: QWebChannelMessageTypes.init}); + }); }; function QObject(name, data, webChannel) |