From 81dac6e848da8e8a071a2069862590889a287067 Mon Sep 17 00:00:00 2001 From: Milian Wolff Date: Thu, 16 Oct 2014 13:37:22 +0200 Subject: 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 --- src/webchannel/qmetaobjectpublisher.cpp | 73 ++++++++++++++++++++------------- src/webchannel/qmetaobjectpublisher_p.h | 11 +++-- src/webchannel/qwebchannel.js | 36 ++++++++++++++-- 3 files changed, 85 insertions(+), 35 deletions(-) (limited to 'src') 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()) { - 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()) { + // 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,11 +154,16 @@ 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. */ 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) { -- cgit v1.2.1 From 6a352361a54afa4b340e88771ff885ee32b932f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lutz=20Sch=C3=B6nemann?= Date: Tue, 30 Sep 2014 16:34:33 +0200 Subject: Fix: Delete objects even when no transport is available The issue is that objects that get deleted will not be removed from the internal lists. This patch checks for destroyed signals in cases when no transport is available (no connection established). cherry picked from commit 037c67962de3d74669c55a9db456ffd38a28480f Change-Id: I24e345dcbfe88e15031d288eb65abb2c3066d4c0 Reviewed-by: Milian Wolff --- src/webchannel/qmetaobjectpublisher.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp index 90c39ff..b3ae23c 100644 --- a/src/webchannel/qmetaobjectpublisher.cpp +++ b/src/webchannel/qmetaobjectpublisher.cpp @@ -368,6 +368,8 @@ QJsonValue QMetaObjectPublisher::invokeMethod(QObject *const object, const int m void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments) { if (!webChannel || webChannel->d_func()->transports.isEmpty()) { + if (signalIndex == s_destroyedSignalIndex) + objectDestroyed(object); return; } if (!signalToPropertyMap.value(object).contains(signalIndex)) { -- cgit v1.2.1 From ed40ffb381e1f874145b70de43961298428c03af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lutz=20Sch=C3=B6nemann?= Date: Mon, 1 Dec 2014 16:12:22 +0100 Subject: Fix crash on signal after deregistration Implemented a remove method in SignalHandler that allows us to remove and disconnect an object from SignalHandler w/o decrementing the connection counter until it hits zero or deleting the object That same functionality was used to remove an object from internal lists when receiving a destroyed signal from an object. In case of deregistering an object we haven't received a destoryed signal but simulated reception of that signal and so that code was not called in that case. Change-Id: Ie20cf628a2de028375f5d29f913682e25ebf8d44 Reviewed-by: Milian Wolff --- src/webchannel/qmetaobjectpublisher.cpp | 1 + src/webchannel/signalhandler_p.h | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp index b3ae23c..b92cf51 100644 --- a/src/webchannel/qmetaobjectpublisher.cpp +++ b/src/webchannel/qmetaobjectpublisher.cpp @@ -403,6 +403,7 @@ void QMetaObjectPublisher::objectDestroyed(const QObject *object) Q_ASSERT(removed); Q_UNUSED(removed); + signalHandler.remove(object); signalToPropertyMap.remove(object); pendingPropertyUpdates.remove(object); wrappedObjects.remove(object); diff --git a/src/webchannel/signalhandler_p.h b/src/webchannel/signalhandler_p.h index 2560cdb..03fbca9 100644 --- a/src/webchannel/signalhandler_p.h +++ b/src/webchannel/signalhandler_p.h @@ -84,6 +84,11 @@ public: */ void clear(); + /** + * Fully remove and disconnect an object from handler + */ + void remove(const QObject *object); + private: /** * Exctract the arguments of a signal call and pass them to the receiver. @@ -252,15 +257,6 @@ int SignalHandler::qt_metacall(QMetaObject::Call call, int methodId, v dispatch(object, methodId, args); - if (methodId == s_destroyedSignalIndex) { - // disconnect on QObject::destroyed - ConnectionHash::iterator it = m_connectionsCounter.find(object); - Q_ASSERT(it != m_connectionsCounter.end()); - foreach (const ConnectionPair &connection, *it) { - QObject::disconnect(connection.first); - } - m_connectionsCounter.erase(it); - } return -1; } return methodId; @@ -280,6 +276,17 @@ void SignalHandler::clear() m_signalArgumentTypes[&QObject::staticMetaObject] = keep; } +template +void SignalHandler::remove(const QObject *object) +{ + Q_ASSERT(m_connectionsCounter.contains(object)); + const SignalConnectionHash &connections = m_connectionsCounter.value(object); + foreach (const ConnectionPair &connection, connections) { + QObject::disconnect(connection.first); + } + m_connectionsCounter.remove(object); +} + QT_END_NAMESPACE #endif // SIGNALHANDLER_H -- cgit v1.2.1 From e41356d8a8b3808a4d4a6df6668812a7eb012378 Mon Sep 17 00:00:00 2001 From: Kai Koehne Date: Thu, 4 Dec 2014 10:47:16 +0100 Subject: Update plugins.qmltypes Also adapt the .pro file so that future updates can just be triggered by 'make qmltypes'. Change-Id: Ide120291c89e4e37991e87663b32be03af0e3f88 Reviewed-by: Milian Wolff --- src/imports/webchannel/plugins.qmltypes | 62 ++++++++++++++++----------------- src/imports/webchannel/webchannel.pro | 2 ++ 2 files changed, 32 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/imports/webchannel/plugins.qmltypes b/src/imports/webchannel/plugins.qmltypes index 789f2b4..4d0155d 100644 --- a/src/imports/webchannel/plugins.qmltypes +++ b/src/imports/webchannel/plugins.qmltypes @@ -4,49 +4,21 @@ import QtQuick.tooling 1.1 // It is used for QML tooling purposes only. // // This file was auto-generated by: -// 'qmlplugindump -notrelocatable QtWebChannel 1.0' +// 'qmlplugindump -nonrelocatable QtWebChannel 1.0' Module { Component { - name: "QWebChannel" - prototype: "QObject" - Property { name: "blockUpdates"; type: "bool" } - Signal { - name: "blockUpdatesChanged" - Parameter { name: "block"; type: "bool" } - } - Method { - name: "sendMessage" - Parameter { name: "id"; type: "QJsonValue" } - Parameter { name: "data"; type: "QJsonValue" } - } - Method { - name: "sendMessage" - Parameter { name: "id"; type: "QJsonValue" } - } - Method { - name: "registerObject" - Parameter { name: "id"; type: "string" } - Parameter { name: "object"; type: "QObject"; isPointer: true } - } - Method { - name: "deregisterObject" - Parameter { name: "object"; type: "QObject"; isPointer: true } - } - } - Component { - name: "QmlWebChannel" + name: "QQmlWebChannel" prototype: "QWebChannel" exports: ["QtWebChannel/WebChannel 1.0"] exportMetaObjectRevisions: [0] - attachedType: "QmlWebChannelAttached" + attachedType: "QQmlWebChannelAttached" Property { name: "transports"; type: "QObject"; isList: true; isReadonly: true } Property { name: "registeredObjects"; type: "QObject"; isList: true; isReadonly: true } Method { name: "registerObjects" Parameter { name: "objects"; type: "QVariantMap" } } - Method { name: "test_clientIsIdle"; type: "bool" } Method { name: "connectTo" Parameter { name: "transport"; type: "QObject"; isPointer: true } @@ -57,7 +29,7 @@ Module { } } Component { - name: "QmlWebChannelAttached" + name: "QQmlWebChannelAttached" prototype: "QObject" Property { name: "id"; type: "string" } Signal { @@ -65,4 +37,30 @@ Module { Parameter { name: "id"; type: "string" } } } + Component { + name: "QWebChannel" + prototype: "QObject" + Property { name: "blockUpdates"; type: "bool" } + Signal { + name: "blockUpdatesChanged" + Parameter { name: "block"; type: "bool" } + } + Method { + name: "connectTo" + Parameter { name: "transport"; type: "QWebChannelAbstractTransport"; isPointer: true } + } + Method { + name: "disconnectFrom" + Parameter { name: "transport"; type: "QWebChannelAbstractTransport"; isPointer: true } + } + Method { + name: "registerObject" + Parameter { name: "id"; type: "string" } + Parameter { name: "object"; type: "QObject"; isPointer: true } + } + Method { + name: "deregisterObject" + Parameter { name: "object"; type: "QObject"; isPointer: true } + } + } } diff --git a/src/imports/webchannel/webchannel.pro b/src/imports/webchannel/webchannel.pro index 8ae6ef5..4c8d322 100644 --- a/src/imports/webchannel/webchannel.pro +++ b/src/imports/webchannel/webchannel.pro @@ -3,6 +3,8 @@ QT = core quick webchannel-private INCLUDEPATH += ../../webchannel VPATH += ../../webchannel +IMPORT_VERSION = 1.0 + SOURCES += \ plugin.cpp -- cgit v1.2.1