diff options
-rw-r--r-- | src/webchannel/qmetaobjectpublisher.cpp | 73 | ||||
-rw-r--r-- | src/webchannel/qmetaobjectpublisher_p.h | 11 | ||||
-rw-r--r-- | src/webchannel/qwebchannel.js | 36 | ||||
-rw-r--r-- | tests/auto/qml/tst_webchannel.qml | 17 |
4 files changed, 101 insertions, 36 deletions
diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp index 27eb134..90c39ff 100644 --- a/src/webchannel/qmetaobjectpublisher.cpp +++ b/src/webchannel/qmetaobjectpublisher.cpp @@ -95,15 +95,18 @@ 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; + if (propertyUpdatesInitialized) { + if (!webChannel->d_func()->transports.isEmpty()) { + 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)); + } } -QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object) const +QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object) { QJsonObject data; if (!object) { @@ -151,7 +154,7 @@ QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object) cons prop.name(), object->metaObject()->className()); } propertyInfo.append(signalInfo); - propertyInfo.append(QJsonValue::fromVariant(prop.read(object))); + propertyInfo.append(wrapResult(prop.read(object))); qtProperties.append(propertyInfo); } for (int i = 0; i < metaObject->methodCount(); ++i) { @@ -289,7 +292,7 @@ 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)] = QJsonValue::fromVariant(property.read(object)); + properties[QString::number(propertyIndex)] = wrapResult(property.read(object)); } sigs[QString::number(sigIt.key())] = QJsonArray::fromVariantList(sigIt.value()); } @@ -374,9 +377,7 @@ void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signal message[KEY_OBJECT] = objectName; message[KEY_SIGNAL] = signalIndex; if (!arguments.isEmpty()) { - // TODO: wrap (new) objects on the fly - QJsonArray args = QJsonArray::fromVariantList(arguments); - message[KEY_ARGS] = args; + message[KEY_ARGS] = wrapList(arguments); } message[KEY_TYPE] = TypeSignal; broadcastMessage(message); @@ -408,33 +409,49 @@ void QMetaObjectPublisher::objectDestroyed(const QObject *object) QJsonValue QMetaObjectPublisher::wrapResult(const QVariant &result) { if (QObject *object = result.value<QObject *>()) { - QJsonObject &objectInfo = wrappedObjects[object]; - if (!objectInfo.isEmpty()) { - // already registered, use cached information - Q_ASSERT(registeredObjectIds.contains(object)); - return objectInfo; - } // else the object is not yet wrapped, do it now - - const QString &id = QUuid::createUuid().toString(); - Q_ASSERT(!registeredObjectIds.contains(object)); - - QJsonObject info = classInfoForObject(object); + QJsonObject objectInfo; objectInfo[KEY_QOBJECT] = true; - objectInfo[KEY_ID] = id; - objectInfo[KEY_DATA] = info; - - registeredObjectIds[object] = id; - registeredObjects[id] = object; - wrappedObjects.insert(object, objectInfo); + QString id = registeredObjectIds.value(object); + if (id.isEmpty()) { + // neither registered, nor wrapped, do so now + id = QUuid::createUuid().toString(); + + 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); + } + } 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); + } - initializePropertyUpdates(object, info); + objectInfo[KEY_ID] = id; return objectInfo; + } else if (result.canConvert<QVariantList>()) { + // recurse and potentially wrap contents of the array + return wrapList(result.toList()); } // no need to wrap this return QJsonValue::fromVariant(result); } +QJsonArray QMetaObjectPublisher::wrapList(const QVariantList &list) +{ + QJsonArray array; + foreach (const QVariant &arg, list) { + array.append(wrapResult(arg)); + } + return array; +} + void QMetaObjectPublisher::deleteWrappedObject(QObject *object) const { if (!wrappedObjects.contains(object)) { diff --git a/src/webchannel/qmetaobjectpublisher_p.h b/src/webchannel/qmetaobjectpublisher_p.h index a7cdda7..05f33bd 100644 --- a/src/webchannel/qmetaobjectpublisher_p.h +++ b/src/webchannel/qmetaobjectpublisher_p.h @@ -94,7 +94,7 @@ public: /** * Serialize the QMetaObject of @p object and return it in JSON form. */ - QJsonObject classInfoForObject(const QObject *object) const; + QJsonObject classInfoForObject(const QObject *object); /** * Set the client to idle or busy, based on the value of @p isIdle. @@ -154,12 +154,17 @@ public: * return the objects class information. * * All other input types are returned as-is. - * - * TODO: support wrapping of initially-registered objects */ QJsonValue wrapResult(const QVariant &result); /** + * 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); + + /** * Invoke delete later on @p object. */ void deleteWrappedObject(QObject *object) const; diff --git a/src/webchannel/qwebchannel.js b/src/webchannel/qwebchannel.js index d2c6525..472330e 100644 --- a/src/webchannel/qwebchannel.js +++ b/src/webchannel/qwebchannel.js @@ -161,6 +161,10 @@ var QWebChannel = function(transport, initCallback) var data = message.data[objectName]; var object = new QObject(objectName, data, channel); } + // now unwrap properties, which might reference other registered objects + for (var objectName in channel.objects) { + channel.objects[objectName].unwrapProperties(); + } if (initCallback) { initCallback(channel); } @@ -190,18 +194,31 @@ function QObject(name, data, webChannel) // ---------------------------------------------------------------------- - function unwrapQObject( response ) + this.unwrapQObject = function(response) { + if (response instanceof Array) { + // support list of objects + var ret = new Array(response.length); + for (var i = 0; i < response.length; ++i) { + ret[i] = object.unwrapQObject(response[i]); + } + return ret; + } if (!response || !response["__QObject*__"] - || response["id"] === undefined - || response["data"] === undefined) { + || response["id"] === undefined) { return response; } + var objectId = response.id; if (webChannel.objects[objectId]) return webChannel.objects[objectId]; + if (!response.data) { + console.error("Cannot unwrap unknown QObject " + objectId + " without data."); + return; + } + var qObject = new QObject( objectId, response.data, webChannel ); qObject.destroyed.connect(function() { if (webChannel.objects[objectId] === qObject) { @@ -219,9 +236,18 @@ function QObject(name, data, webChannel) } } }); + // here we are already initialized, and thus must directly unwrap the properties + qObject.unwrapProperties(); return qObject; } + this.unwrapProperties = function() + { + for (var propertyIdx in object.__propertyCache__) { + object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]); + } + } + function addSignal(signalData, isPropertyNotifySignal) { var signalName = signalData[0]; @@ -324,7 +350,7 @@ function QObject(name, data, webChannel) "args": args }, function(response) { if (response !== undefined) { - var result = unwrapQObject(response); + var result = object.unwrapQObject(response); if (callback) { (callback)(result); } @@ -339,6 +365,8 @@ function QObject(name, data, webChannel) var propertyName = propertyInfo[1]; var notifySignalData = propertyInfo[2]; // initialize property cache with current value + // NOTE: if this is an object, it is not directly unwrapped as it might + // reference other QObject that we do not know yet object.__propertyCache__[propertyIndex] = propertyInfo[3]; if (notifySignalData) { diff --git a/tests/auto/qml/tst_webchannel.qml b/tests/auto/qml/tst_webchannel.qml index f304197..e7fcedc 100644 --- a/tests/auto/qml/tst_webchannel.qml +++ b/tests/auto/qml/tst_webchannel.qml @@ -66,6 +66,8 @@ TestCase { property var bar: 1 WebChannel.id: "myOtherObj" } + QtObject{ id: bar; objectName: "bar" } + QtObject{ id: baz; objectName: "baz" } QtObject { id: myFactory property var lastObj @@ -74,9 +76,13 @@ TestCase { lastObj = component.createObject(myFactory, {objectName: id}); return lastObj; } + property var objectInProperty: QtObject { + objectName: "foo" + } + property var otherObject: myObj + property var objects: [ bar, baz ]; WebChannel.id: "myFactory" } - Component { id: component QtObject { @@ -264,6 +270,15 @@ TestCase { myFactory.lastObj.mySignal("foobar", 42); + // property should be wrapped + compare(channel.objects.myFactory.objectInProperty.objectName, "foo"); + // list property as well + compare(channel.objects.myFactory.objects.length, 2); + compare(channel.objects.myFactory.objects[0].objectName, "bar"); + compare(channel.objects.myFactory.objects[1].objectName, "baz"); + // also works with properties that reference other registered objects + compare(channel.objects.myFactory.otherObject, channel.objects.myObj); + // deleteLater call msg = client.awaitMessage(); compare(msg.type, JSClient.QWebChannelMessageTypes.invokeMethod); |