summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/webchannel/qmetaobjectpublisher.cpp175
-rw-r--r--src/webchannel/qmetaobjectpublisher_p.h40
-rw-r--r--src/webchannel/qwebchannel.js29
3 files changed, 149 insertions, 95 deletions
diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp
index b92cf51..0c22798 100644
--- a/src/webchannel/qmetaobjectpublisher.cpp
+++ b/src/webchannel/qmetaobjectpublisher.cpp
@@ -41,6 +41,7 @@
#include <QDebug>
#include <QJsonObject>
#include <QJsonArray>
+#include <QJSValue>
#include <QUuid>
QT_BEGIN_NAMESPACE
@@ -73,6 +74,15 @@ const QString KEY_ARGS = QStringLiteral("args");
const QString KEY_PROPERTY = QStringLiteral("property");
const QString KEY_VALUE = QStringLiteral("value");
+QJsonObject createResponse(const QJsonValue &id, const QJsonValue &data)
+{
+ QJsonObject response;
+ response[KEY_TYPE] = TypeResponse;
+ response[KEY_ID] = id;
+ response[KEY_DATA] = data;
+ return response;
+}
+
/// TODO: what is the proper value here?
const int PROPERTY_UPDATE_INTERVAL = 50;
}
@@ -83,7 +93,6 @@ QMetaObjectPublisher::QMetaObjectPublisher(QWebChannel *webChannel)
, signalHandler(this)
, clientIsIdle(false)
, blockUpdates(false)
- , pendingInit(false)
, propertyUpdatesInitialized(false)
{
}
@@ -102,11 +111,11 @@ void QMetaObjectPublisher::registerObject(const QString &id, QObject *object)
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));
+ initializePropertyUpdates(object, classInfoForObject(object, Q_NULLPTR));
}
}
-QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object)
+QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object, QWebChannelAbstractTransport *transport)
{
QJsonObject data;
if (!object) {
@@ -154,7 +163,7 @@ QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object)
prop.name(), object->metaObject()->className());
}
propertyInfo.append(signalInfo);
- propertyInfo.append(wrapResult(prop.read(object)));
+ propertyInfo.append(wrapResult(prop.read(object), transport));
qtProperties.append(propertyInfo);
}
for (int i = 0; i < metaObject->methodCount(); ++i) {
@@ -211,29 +220,21 @@ void QMetaObjectPublisher::setClientIsIdle(bool isIdle)
}
}
-void QMetaObjectPublisher::initializeClients()
+QJsonObject QMetaObjectPublisher::initializeClient(QWebChannelAbstractTransport *transport)
{
- if (!webChannel) {
- return;
- }
-
QJsonObject objectInfos;
{
const QHash<QString, QObject *>::const_iterator end = registeredObjects.constEnd();
for (QHash<QString, QObject *>::const_iterator it = registeredObjects.constBegin(); it != end; ++it) {
- const QJsonObject &info = classInfoForObject(it.value());
+ const QJsonObject &info = classInfoForObject(it.value(), transport);
if (!propertyUpdatesInitialized) {
initializePropertyUpdates(it.value(), info);
}
objectInfos[it.key()] = info;
}
}
- QJsonObject message;
- message[KEY_TYPE] = TypeInit;
- message[KEY_DATA] = objectInfos;
- broadcastMessage(message);
propertyUpdatesInitialized = true;
- pendingInit = false;
+ return objectInfos;
}
void QMetaObjectPublisher::initializePropertyUpdates(const QObject *const object, const QJsonObject &objectInfo)
@@ -275,12 +276,14 @@ void QMetaObjectPublisher::sendPendingPropertyUpdates()
}
QJsonArray data;
+ QHash<QWebChannelAbstractTransport*, QJsonArray> specificUpdates;
// convert pending property updates to JSON data
const PendingPropertyUpdates::const_iterator end = pendingPropertyUpdates.constEnd();
for (PendingPropertyUpdates::const_iterator it = pendingPropertyUpdates.constBegin(); it != end; ++it) {
const QObject *object = it.key();
const QMetaObject *const metaObject = object->metaObject();
+ const QString objectId = registeredObjectIds.value(object);
const SignalToPropertyNameMap &objectsSignalToPropertyMap = signalToPropertyMap.value(object);
// maps property name to current property value
QJsonObject properties;
@@ -292,26 +295,47 @@ 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)] = wrapResult(property.read(object));
+ properties[QString::number(propertyIndex)] = wrapResult(property.read(object), Q_NULLPTR, objectId);
}
sigs[QString::number(sigIt.key())] = QJsonArray::fromVariantList(sigIt.value());
}
QJsonObject obj;
- obj[KEY_OBJECT] = registeredObjectIds.value(object);
+ obj[KEY_OBJECT] = objectId;
obj[KEY_SIGNALS] = sigs;
obj[KEY_PROPERTIES] = properties;
- data.push_back(obj);
+
+ // if the object is auto registered, just send the update only to clients which know this object
+ if (wrappedObjects.contains(objectId)) {
+ foreach (QWebChannelAbstractTransport *transport, wrappedObjects.value(objectId).transports) {
+ QJsonArray &arr = specificUpdates[transport];
+ arr.push_back(obj);
+ }
+ } else {
+ data.push_back(obj);
+ }
}
pendingPropertyUpdates.clear();
QJsonObject message;
message[KEY_TYPE] = TypePropertyUpdate;
- message[KEY_DATA] = data;
- setClientIsIdle(false);
- broadcastMessage(message);
+
+ // data does not contain specific updates
+ if (!data.isEmpty()) {
+ setClientIsIdle(false);
+
+ message[KEY_DATA] = data;
+ broadcastMessage(message);
+ }
+
+ // send every property update which is not supposed to be broadcasted
+ const QHash<QWebChannelAbstractTransport*, QJsonArray>::const_iterator suend = specificUpdates.constEnd();
+ for (QHash<QWebChannelAbstractTransport*, QJsonArray>::const_iterator it = specificUpdates.constBegin(); it != suend; ++it) {
+ message[KEY_DATA] = it.value();
+ it.key()->sendMessage(message);
+ }
}
-QJsonValue QMetaObjectPublisher::invokeMethod(QObject *const object, const int methodIndex,
+QVariant QMetaObjectPublisher::invokeMethod(QObject *const object, const int methodIndex,
const QJsonArray &args)
{
const QMetaMethod &method = object->metaObject()->method(methodIndex);
@@ -362,7 +386,7 @@ QJsonValue QMetaObjectPublisher::invokeMethod(QObject *const object, const int m
arguments[0], arguments[1], arguments[2], arguments[3], arguments[4],
arguments[5], arguments[6], arguments[7], arguments[8], arguments[9]);
- return wrapResult(returnValue);
+ return returnValue;
}
void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments)
@@ -379,10 +403,18 @@ void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signal
message[KEY_OBJECT] = objectName;
message[KEY_SIGNAL] = signalIndex;
if (!arguments.isEmpty()) {
- message[KEY_ARGS] = wrapList(arguments);
+ message[KEY_ARGS] = wrapList(arguments, Q_NULLPTR, objectName);
}
message[KEY_TYPE] = TypeSignal;
- broadcastMessage(message);
+
+ // if the object is wrapped, just send the response to clients which know this object
+ if (wrappedObjects.contains(objectName)) {
+ foreach (QWebChannelAbstractTransport *transport, wrappedObjects.value(objectName).transports) {
+ transport->sendMessage(message);
+ }
+ } else {
+ broadcastMessage(message);
+ }
if (signalIndex == s_destroyedSignalIndex) {
objectDestroyed(object);
@@ -399,65 +431,87 @@ 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);
signalHandler.remove(object);
signalToPropertyMap.remove(object);
pendingPropertyUpdates.remove(object);
- wrappedObjects.remove(object);
}
-QJsonValue QMetaObjectPublisher::wrapResult(const QVariant &result)
+// NOTE: transport can be a nullptr
+// in such a case, we need to ensure that the property is registered to
+// the target transports of the parentObjectId
+QJsonValue QMetaObjectPublisher::wrapResult(const QVariant &result, QWebChannelAbstractTransport *transport,
+ const QString &parentObjectId)
{
if (QObject *object = result.value<QObject *>()) {
- QJsonObject objectInfo;
- objectInfo[KEY_QOBJECT] = true;
QString id = registeredObjectIds.value(object);
+ QJsonObject classInfo;
if (id.isEmpty()) {
// neither registered, nor wrapped, do so now
id = QUuid::createUuid().toString();
+ Q_ASSERT(!registeredObjects.contains(id));
+
+ classInfo = classInfoForObject(object, transport);
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);
+
+ ObjectInfo oi(object, classInfo);
+ if (transport) {
+ oi.transports.append(transport);
+ } else {
+ // use the transports from the parent object
+ oi.transports = wrappedObjects.value(parentObjectId).transports;
+ // or fallback to all transports if the parent is not wrapped
+ if (oi.transports.isEmpty())
+ oi.transports = webChannel->d_func()->transports;
}
- } 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);
+ wrappedObjects.insert(id, oi);
+
+ initializePropertyUpdates(object, classInfo);
+ } else if (wrappedObjects.contains(id)) {
+ Q_ASSERT(object == wrappedObjects.value(id).object);
+ // check if this transport is already assigned to the object
+ if (transport && !wrappedObjects.value(id).transports.contains(transport))
+ wrappedObjects[id].transports.append(transport);
+ classInfo = wrappedObjects.value(id).classinfo;
}
+ QJsonObject objectInfo;
+ objectInfo[KEY_QOBJECT] = true;
objectInfo[KEY_ID] = id;
+ if (!classInfo.isEmpty())
+ objectInfo[KEY_DATA] = classInfo;
return objectInfo;
+ } else if (result.canConvert<QJSValue>()) {
+ // Workaround for keeping QJSValues from QVariant.
+ // Calling QJSValue::toVariant() converts JS-objects/arrays to QVariantMap/List
+ // instead of stashing a QJSValue itself into a variant.
+ // TODO: Improve QJSValue-QJsonValue conversion in Qt.
+ return wrapResult(result.value<QJSValue>().toVariant(), transport, parentObjectId);
} else if (result.canConvert<QVariantList>()) {
// recurse and potentially wrap contents of the array
- return wrapList(result.toList());
+ return wrapList(result.toList(), transport);
}
- // no need to wrap this
return QJsonValue::fromVariant(result);
}
-QJsonArray QMetaObjectPublisher::wrapList(const QVariantList &list)
+QJsonArray QMetaObjectPublisher::wrapList(const QVariantList &list, QWebChannelAbstractTransport *transport, const QString &parentObjectId)
{
QJsonArray array;
foreach (const QVariant &arg, list) {
- array.append(wrapResult(arg));
+ array.append(wrapResult(arg, transport, parentObjectId));
}
return array;
}
void QMetaObjectPublisher::deleteWrappedObject(QObject *object) const
{
- if (!wrappedObjects.contains(object)) {
+ if (!wrappedObjects.contains(registeredObjectIds.value(object))) {
qWarning() << "Not deleting non-wrapped object" << object;
return;
}
@@ -492,17 +546,21 @@ void QMetaObjectPublisher::handleMessage(const QJsonObject &message, QWebChannel
if (type == TypeIdle) {
setClientIsIdle(true);
} else if (type == TypeInit) {
- if (!blockUpdates) {
- initializeClients();
- } else {
- pendingInit = true;
+ if (!message.contains(KEY_ID)) {
+ qWarning("JSON message object is missing the id property: %s",
+ QJsonDocument(message).toJson().constData());
+ return;
}
+ transport->sendMessage(createResponse(message.value(KEY_ID), initializeClient(transport)));
} else if (type == TypeDebug) {
static QTextStream out(stdout);
out << "DEBUG: " << message.value(KEY_DATA).toString() << endl;
} 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;
@@ -514,11 +572,10 @@ void QMetaObjectPublisher::handleMessage(const QJsonObject &message, QWebChannel
QJsonDocument(message).toJson().constData());
return;
}
- QJsonObject response;
- response[KEY_TYPE] = TypeResponse;
- response[KEY_ID] = message.value(KEY_ID);
- response[KEY_DATA] = invokeMethod(object, message.value(KEY_METHOD).toInt(-1), message.value(KEY_ARGS).toArray());
- transport->sendMessage(response);
+
+ transport->sendMessage(createResponse(message.value(KEY_ID),
+ wrapResult(invokeMethod(object, message.value(KEY_METHOD).toInt(-1),
+ message.value(KEY_ARGS).toArray()), transport)));
} else if (type == TypeConnectToSignal) {
signalHandler.connectTo(object, message.value(KEY_SIGNAL).toInt(-1));
} else if (type == TypeDisconnectFromSignal) {
@@ -544,11 +601,7 @@ void QMetaObjectPublisher::setBlockUpdates(bool block)
blockUpdates = block;
if (!blockUpdates) {
- if (pendingInit) {
- initializeClients();
- } else {
- sendPendingPropertyUpdates();
- }
+ sendPendingPropertyUpdates();
} else if (timer.isActive()) {
timer.stop();
}
diff --git a/src/webchannel/qmetaobjectpublisher_p.h b/src/webchannel/qmetaobjectpublisher_p.h
index 05f33bd..359135c 100644
--- a/src/webchannel/qmetaobjectpublisher_p.h
+++ b/src/webchannel/qmetaobjectpublisher_p.h
@@ -40,6 +40,8 @@
#include <QStringList>
#include <QMetaObject>
#include <QBasicTimer>
+#include <QPointer>
+#include <QJsonObject>
#include "qwebchannelglobal.h"
@@ -70,7 +72,6 @@ class QWebChannelAbstractTransport;
class Q_WEBCHANNEL_EXPORT QMetaObjectPublisher : public QObject
{
Q_OBJECT
-
public:
explicit QMetaObjectPublisher(QWebChannel *webChannel);
virtual ~QMetaObjectPublisher();
@@ -94,7 +95,7 @@ public:
/**
* Serialize the QMetaObject of @p object and return it in JSON form.
*/
- QJsonObject classInfoForObject(const QObject *object);
+ QJsonObject classInfoForObject(const QObject *object, QWebChannelAbstractTransport *transport);
/**
* Set the client to idle or busy, based on the value of @p isIdle.
@@ -108,7 +109,7 @@ public:
*
* Furthermore, if that was not done already, connect to their property notify signals.
*/
- void initializeClients();
+ QJsonObject initializeClient(QWebChannelAbstractTransport *transport);
/**
* Go through all properties of the given object and connect to their notify signal.
@@ -135,7 +136,7 @@ public:
* The return value of the method invocation is then serialized and a response message
* is returned.
*/
- QJsonValue invokeMethod(QObject *const object, const int methodIndex, const QJsonArray &args);
+ QVariant invokeMethod(QObject *const object, const int methodIndex, const QJsonArray &args);
/**
* Callback of the signalHandler which forwards the signal invocation to the webchannel clients.
@@ -155,14 +156,16 @@ public:
*
* All other input types are returned as-is.
*/
- QJsonValue wrapResult(const QVariant &result);
+ QJsonValue wrapResult(const QVariant &result, QWebChannelAbstractTransport *transport,
+ const QString &parentObjectId = QString());
/**
* 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);
+ QJsonArray wrapList(const QVariantList &list, QWebChannelAbstractTransport *transport,
+ const QString &parentObjectId = QString());
/**
* Invoke delete later on @p object.
@@ -200,10 +203,6 @@ private:
// true when no property updates should be sent, false otherwise
bool blockUpdates;
- // true when at least one client needs to be initialized,
- // i.e. when a Qt.init came in which was not handled yet.
- bool pendingInit;
-
// true when at least one client was initialized and thus
// the property updates have been initialized and the
// object info map set.
@@ -215,6 +214,24 @@ private:
// Map the registered objects to their id.
QHash<const QObject *, QString> registeredObjectIds;
+ // Groups individually wrapped objects with their class information and the transports that have access to it.
+ struct ObjectInfo
+ {
+ ObjectInfo()
+ : object(Q_NULLPTR)
+ {}
+ ObjectInfo(QObject *o, const QJsonObject &i)
+ : object(o)
+ , classinfo(i)
+ {}
+ QObject *object;
+ QJsonObject classinfo;
+ QVector<QWebChannelAbstractTransport*> transports;
+ };
+
+ // Map of objects wrapped from invocation returns
+ QHash<QString, ObjectInfo> wrappedObjects;
+
// Map of objects to maps of signal indices to a set of all their property indices.
// The last value is a set as a signal can be the notify signal of multiple properties.
typedef QHash<int, QSet<int> > SignalToPropertyNameMap;
@@ -226,9 +243,6 @@ private:
typedef QHash<const QObject *, SignalToArgumentsMap> PendingPropertyUpdates;
PendingPropertyUpdates pendingPropertyUpdates;
- // Maps wrapped object to class info
- QHash<const QObject *, QJsonObject> 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
// prevent message flooding.
diff --git a/src/webchannel/qwebchannel.js b/src/webchannel/qwebchannel.js
index 472330e..4a1c784 100644
--- a/src/webchannel/qwebchannel.js
+++ b/src/webchannel/qwebchannel.js
@@ -82,9 +82,6 @@ var QWebChannel = function(transport, initCallback)
case QWebChannelMessageTypes.propertyUpdate:
channel.handlePropertyUpdate(data);
break;
- case QWebChannelMessageTypes.init:
- channel.handleInit(data);
- break;
default:
console.error("invalid message received:", message.data);
break;
@@ -149,17 +146,14 @@ var QWebChannel = function(transport, initCallback)
channel.exec({type: QWebChannelMessageTypes.idle});
}
- // prevent multiple initialization which might happen with multiple webchannel clients.
- this.initialized = false;
- this.handleInit = function(message)
+ this.debug = function(message)
{
- if (channel.initialized) {
- return;
- }
- channel.initialized = true;
- for (var objectName in message.data) {
- var data = message.data[objectName];
- var object = new QObject(objectName, data, channel);
+ channel.send({type: QWebChannelMessageTypes.debug, data: message});
+ };
+
+ channel.exec({type: QWebChannelMessageTypes.init}, function(data) {
+ for (var objectName in data) {
+ var object = new QObject(objectName, data[objectName], channel);
}
// now unwrap properties, which might reference other registered objects
for (var objectName in channel.objects) {
@@ -169,14 +163,7 @@ var QWebChannel = function(transport, initCallback)
initCallback(channel);
}
channel.exec({type: QWebChannelMessageTypes.idle});
- }
-
- this.debug = function(message)
- {
- channel.send({type: QWebChannelMessageTypes.debug, data: message});
- };
-
- channel.exec({type: QWebChannelMessageTypes.init});
+ });
};
function QObject(name, data, webChannel)