summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMilian Wolff <milian.wolff@kdab.com>2014-10-16 13:37:22 +0200
committerMilian Wolff <milian.wolff@kdab.com>2014-12-02 14:16:55 +0100
commit81dac6e848da8e8a071a2069862590889a287067 (patch)
treeb8bbca884fd5e04755f6feaa7310e2d02c5fac34
parent86d77a900852691267f556fbde98406a12ee4310 (diff)
downloadqtwebchannel-81dac6e848da8e8a071a2069862590889a287067.tar.gz
Make objects inside properties accessible.
Similar to the support for factory-methods, we must wrap objects in properties to make them accessible to clients. This patch adds the required code for that. Besides support for simple properties that reference an object, this patch also adds support for list properties that contain objects. The client-side unwrap of properties is delayed until all objects are initialized, as a property might reference another registered object. Change-Id: I9fb90a8eab4c66d2f4231fdb482e0d97d128df3e Reviewed-by: Milian Wolff <milian.wolff@kdab.com>
-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);