From 9fdce8e443030ab99d31e42fffc977cf284c36c4 Mon Sep 17 00:00:00 2001 From: Sumedha Widyadharma Date: Thu, 7 Aug 2014 16:05:05 +0200 Subject: Separate registered and autoregistered QObjects Currently, a new client gets a list of _all_ registered QObjects, whether they were explicitly registered or not. This leaks internal information which the clients cannot use right away anyway. Change-Id: I4b25a731e9bc2d646f903057b409aecd34dc7f11 Reviewed-by: Milian Wolff --- src/webchannel/qmetaobjectpublisher.cpp | 53 +++++++----- src/webchannel/qmetaobjectpublisher_p.h | 10 ++- tests/auto/qml/Client.qml | 109 +++++++++++++++--------- tests/auto/qml/qml.pro | 4 +- tests/auto/qml/tst_multiclient.qml | 143 +++++++++++++++++++++++++++++++- 5 files changed, 254 insertions(+), 65 deletions(-) diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp index 527b3df..3e78ebc 100644 --- a/src/webchannel/qmetaobjectpublisher.cpp +++ b/src/webchannel/qmetaobjectpublisher.cpp @@ -398,38 +398,42 @@ 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); signalToPropertyMap.remove(object); pendingPropertyUpdates.remove(object); - wrappedObjects.remove(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); - objectInfo[KEY_QOBJECT] = true; - objectInfo[KEY_ID] = id; - objectInfo[KEY_DATA] = info; - - registeredObjectIds[object] = id; - registeredObjects[id] = object; - wrappedObjects.insert(object, objectInfo); - - initializePropertyUpdates(object, info); + QString id = registeredObjectIds.value(object); + + QJsonObject objectInfo; + + if (!id.isEmpty() && wrappedObjects.contains(id)) { + Q_ASSERT(object == wrappedObjects.value(id).object); + return wrappedObjects.value(id).info; + } else { + id = QUuid::createUuid().toString(); + + QJsonObject info = classInfoForObject(object); + objectInfo[KEY_QOBJECT] = true; + objectInfo[KEY_ID] = id; + objectInfo[KEY_DATA] = info; + + if (!registeredObjects.contains(id)) { + registeredObjectIds[object] = id; + ObjectInfo oi = { object, objectInfo }; + wrappedObjects.insert(id, oi); + + initializePropertyUpdates(object, info); + } + } + return objectInfo; } @@ -439,7 +443,7 @@ QJsonValue QMetaObjectPublisher::wrapResult(const QVariant &result) void QMetaObjectPublisher::deleteWrappedObject(QObject *object) const { - if (!wrappedObjects.contains(object)) { + if (!wrappedObjects.contains(registeredObjectIds.value(object))) { qWarning() << "Not deleting non-wrapped object" << object; return; } @@ -486,6 +490,9 @@ void QMetaObjectPublisher::handleMessage(const QJsonObject &message, QWebChannel } 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; diff --git a/src/webchannel/qmetaobjectpublisher_p.h b/src/webchannel/qmetaobjectpublisher_p.h index eda17d9..6ba5ee7 100644 --- a/src/webchannel/qmetaobjectpublisher_p.h +++ b/src/webchannel/qmetaobjectpublisher_p.h @@ -40,6 +40,8 @@ #include #include #include +#include +#include #include "qwebchannelglobal.h" @@ -217,8 +219,14 @@ private: typedef QHash PendingPropertyUpdates; PendingPropertyUpdates pendingPropertyUpdates; + // Struct containing the object itself and its ObjectInfo + struct ObjectInfo { + QObject* object; + QJsonValue info; + }; + // Maps wrapped object to class info - QHash wrappedObjects; + QHash 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 diff --git a/tests/auto/qml/Client.qml b/tests/auto/qml/Client.qml index 20da8f6..6e12993 100644 --- a/tests/auto/qml/Client.qml +++ b/tests/auto/qml/Client.qml @@ -39,12 +39,15 @@ import QtWebChannel.Tests 1.0 import "qrc:///qtwebchannel/qwebchannel.js" as JSClient Item { + id: root + TestTransport { id: serverTransport } readonly property var serverTransport: serverTransport property var clientMessages: [] + property var serverMessages: [] property bool debug: false @@ -55,24 +58,21 @@ Item { function send(message) { - if (debug) { - console.log("client posts message: ", message, "is idle:", webChannel.clientIsIdle()); - } - clientMessages.push(message); + if (debug) + console.log("client", (root.objectName ? "(" + root.objectName + ")" : ""), "posts message: ", message, "is idle:", webChannel.clientIsIdle()); + clientMessages.push(JSON.parse(message)); serverTransport.receiveMessage(message); - if (message && message.type && message.type === JSClient.QWebChannelMessageTypes.idle) { + if (message && message.type && message.type === JSClient.QWebChannelMessageTypes.idle) verify(webChannel.clientIsIdle()); - } } Component.onCompleted: { - serverTransport.sendMessageRequested.connect(function(message) { - if (debug) { - console.log("client received message: ", JSON.stringify(message)); - } - if (onmessage) { + serverTransport.sendMessageRequested.connect(function receive(message) { + if (debug) + console.log("client", (root.objectName ? "(" + root.objectName + ")" : ""), "received message:", JSON.stringify(message)); + serverMessages.push(message); + if (onmessage) onmessage({data:message}); - } }); } } @@ -86,51 +86,82 @@ Item { function cleanup() { clientMessages = []; + serverMessages = []; } - function awaitRawMessage() + function awaitRawMessage(from) { - for (var i = 0; i < 10 && !clientMessages.length; ++i) { + if (!from || typeof from !== "string") + from = "clientMessages"; + else + from += "Messages"; + + for (var i = 0; i < 10 && !root[from].length; ++i) wait(10); - } - return clientMessages.shift(); + return root[from].shift(); } - function awaitMessage() + function awaitMessage(from) { - var msg = awaitRawMessage() - if (debug) { - console.log("handling message: ", msg); - } - if (!msg) { + var msg = awaitRawMessage(from) + if (debug) + console.log((root.objectName ? "(" + root.objectName + ")" : ""), "handling message: ", JSON.stringify(msg)); + if (!msg) return false; + return msg; + } + + function await(type, from, skip) { + var msg; + do { + msg = awaitMessage(); + verify(msg); + } while (skip && (msg.type === JSClient.QWebChannelMessageTypes.idle)); + if (type !== null) { + verify(msg); + verify(msg.type); + compare(msg.type, type); } - return JSON.parse(msg); + return msg; + } + + function awaitInit() { + return await(JSClient.QWebChannelMessageTypes.init); + } + + function awaitIdle() { + return await(JSClient.QWebChannelMessageTypes.idle); + } + + function awaitMessageSkipIdle() { + return awaitFunc(null, null, true); + } + + function awaitServerInit() { + return await(JSClient.QWebChannelMessageTypes.init, "server"); } - function awaitInit() + function awaitSignal() { - var msg = awaitMessage(); - verify(msg); - verify(msg.type); - compare(msg.type, JSClient.QWebChannelMessageTypes.init); + return await(JSClient.QWebChannelMessageTypes.signal, "server"); } - function awaitIdle() + function awaitPropertyUpdate() { - var msg = awaitMessage(); - verify(msg); - compare(msg.type, JSClient.QWebChannelMessageTypes.idle); + return await(JSClient.QWebChannelMessageTypes.propertyUpdate, "server"); } - function awaitMessageSkipIdle() + function awaitResponse() { - var msg; - do { - msg = awaitMessage(); - verify(msg); - } while (msg.type === JSClient.QWebChannelMessageTypes.idle); - return msg; + return await(JSClient.QWebChannelMessageTypes.response, "server"); } + function skipToMessage(type, from, max) { + do { + var msg = awaitMessage(from); + if (msg && msg.type === type) + return msg + } while (--max > 0); + return false; + } } diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro index b0c52b5..89c5f56 100644 --- a/tests/auto/qml/qml.pro +++ b/tests/auto/qml/qml.pro @@ -17,9 +17,11 @@ HEADERS += \ testwebchannel.h OTHER_FILES += \ + Client.qml \ WebChannelTest.qml \ tst_webchannel.qml \ tst_metaobjectpublisher.qml \ - tst_bench.qml + tst_bench.qml \ + tst_multiclient.qml \ TESTDATA = data/* diff --git a/tests/auto/qml/tst_multiclient.qml b/tests/auto/qml/tst_multiclient.qml index b696391..115857d 100644 --- a/tests/auto/qml/tst_multiclient.qml +++ b/tests/auto/qml/tst_multiclient.qml @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff +** Copyright (C) 2014 basysKom GmbH, info@basyskom.com, author Lutz Schönemann ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtWebChannel module of the Qt Toolkit. @@ -37,6 +38,8 @@ import QtTest 1.0 import QtWebChannel 1.0 import QtWebChannel.Tests 1.0 +import "qrc:///qtwebchannel/qwebchannel.js" as JSClient + TestCase { name: "MultiClient" @@ -62,18 +65,85 @@ TestCase { WebChannel.id: "foo" } + property var lastMethodArg + QtObject { + id: myObj + property int myProperty: 1 + + signal mySignal(var arg) + + function myMethod(arg) + { + lastMethodArg = arg; + } + + WebChannel.id: "myObj" + } + + QtObject { + id: myOtherObj + property var foo: 1 + property var bar: 1 + WebChannel.id: "myOtherObj" + } + + QtObject { + id: myFactory + property var lastObj + property var createdObjects: [] + + function cleanup() { + while (createdObjects.length) { + var obj = createdObjects.shift(); + if (obj) { + obj.destroy(); + } + } + } + + function create(id) + { + lastObj = component.createObject(myFactory, {objectName: id}); + createdObjects.push(lastObj); + return lastObj; + } + WebChannel.id: "myFactory" + } + + Component { + id: component + QtObject { + property var myProperty : 0 + function myMethod(arg) { + lastMethodArg = arg; + } + signal mySignal(var arg1, var arg2) + } + } + TestWebChannel { id: webChannel transports: [client1.serverTransport, client2.serverTransport] - registeredObjects: [foo] + registeredObjects: [foo, myObj, myOtherObj, myFactory] } function init() { + myObj.myProperty = 1 client1.cleanup(); client2.cleanup(); } + function cleanup() { + client1.debug = false; + client2.debug = false; + // delete all created objects + myFactory.cleanup(); + myFactory.lastObj = undefined; + // reschedule current task to end of event loop + wait(1); + } + function clientInitCallback(channel) { channel.objects.foo.ping.connect(function() { @@ -103,4 +173,75 @@ TestCase { compare(c1.pongAnswer, 1); compare(c2.pongAnswer, 2); } + + function test_autowrappedObjectsNotInInit() { + var testObj1; + var testObj1Id; + + var channel1 = client1.createChannel(function (channel1) { + channel1.objects.myFactory.create("testObj1", function (obj1) { + testObj1 = myFactory.lastObj; + testObj1Id = obj1.__id__; + + // create second channel after factory has created first + // object to make sure that a dynamically created object + // exists but does not get exposed to new channels + createSecondChannel(); + }); + }); + var channel2; + function createSecondChannel() { + // dismiss all messges received before channel creation + client2.cleanup(); + + channel2 = client2.createChannel(function (channel2) { + }); + } + + client1.awaitInit(); + var msg1 = client1.awaitMessage(); + compare(msg1.type, JSClient.QWebChannelMessageTypes.invokeMethod); // create + + client1.awaitIdle(); + + client2.awaitInit(); + client2.awaitIdle(); + + compare(typeof channel2.objects[testObj1Id], "undefined") + } + + function test_autowrappedObjectsNotBroadcasted() { + var testObj2; + var testObj2Id; + + var channel1 = client1.createChannel(function (channel1) { + // create second channel after first channel to make sure + // that a dynamically created object do not get exposed to + // existing channels + createSecondChannel(); + }); + var channel2; + function createSecondChannel() { + // dismiss all messges received before channel creation + client2.cleanup(); + + channel2 = client2.createChannel(function (channel2) { + channel2.objects.myFactory.create("testObj2", function (obj2) { + testObj2 = myFactory.lastObj; + testObj2Id = obj2.__id__; + }); + }); + } + + client1.awaitInit(); + client1.awaitIdle(); + + client2.awaitInit(); + var msg2 = client2.awaitMessage(); + compare(msg2.type, JSClient.QWebChannelMessageTypes.invokeMethod); // create + + client2.awaitIdle(); + + compare(typeof channel1.objects[testObj2Id], "undefined") + } } -- cgit v1.2.1