summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/webchannel/qmetaobjectpublisher.cpp73
-rw-r--r--src/webchannel/qmetaobjectpublisher_p.h11
-rw-r--r--src/webchannel/qwebchannel.js36
-rw-r--r--tests/auto/qml/tst_webchannel.qml17
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);