summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMilian Wolff <milian.wolff@kdab.com>2013-10-31 16:14:28 +0100
committerPierre Rossi <pierre.rossi@gmail.com>2013-11-01 13:57:52 +0100
commita607c948860782b5a095fe802f3acdd5aaf6a568 (patch)
tree8fbd8bd773acfa7fe438fe95af2588485d8e4269 /src
parent708c0dd39d9f2a6f7d699364c6991d78bd30f9a8 (diff)
downloadqtwebchannel-a607c948860782b5a095fe802f3acdd5aaf6a568.tar.gz
Greatly optimize WebChannel in various ways.
This is a big code drop - sorry for that. The benefits are worth it though, I'm sure. The optimizations were required to make the WebChannel useable even on a low-end embedded device with medium amount of traffic. The changes in this patch can be grouped into different parts: a) Do more in C++: Esp. by leveraging e.g. the new classInfoForObjects in QtMetaObjectPublisher (on the C++ side) one can greatly reduce the time required for initialization of the webchannel. b) Property Caching: Instead of requiring a socket roundtrip whenever a property is read on the HTML side, we now cache the property values on the HTML side. Note that for this to work properly, one needs to add proper notify signals to the property declarations, as otherwise the cache will not get updated. c) Grouping: Instead of sending separate messages to the clients for every property update, these signals are grouped by a 50ms timer, and then send aggregated to the client. This reduces the socket traffic, as more boiler plate can be shared. d) Compression: Some data was previously send repeatedly, such as property name and notify signal. This is now compressed internally where possible (i.e. for the ${propName}Changed naming scheme). e) Message Flood Prevention: Previously, one could easily flood an HTML client by sending data to it. If it could not work off the incoming stream one would freeze the HTML client. Now, we wait for an idle signal of the client prior to sending new data to it. Paired with the message grouping and property cache mentioned above, we are able to only send the newest data once the HTML client becomes active again. I.e. we discard now-obsolete property updates etc. Change-Id: I8f3ae16ed6c1f6a89b644acdce7efbf0f07fc786 Reviewed-by: Pierre Rossi <pierre.rossi@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/MetaObjectPublisher.qml266
-rw-r--r--src/qobject.js204
-rw-r--r--src/qtmetaobjectpublisher.cpp85
-rw-r--r--src/qtmetaobjectpublisher.h10
-rw-r--r--src/src.pro2
-rw-r--r--src/webchannel.js2
6 files changed, 460 insertions, 109 deletions
diff --git a/src/MetaObjectPublisher.qml b/src/MetaObjectPublisher.qml
index 6ac588c..2b97c02 100644
--- a/src/MetaObjectPublisher.qml
+++ b/src/MetaObjectPublisher.qml
@@ -44,17 +44,57 @@ import Qt.labs.WebChannel 1.0
MetaObjectPublisherImpl
{
+ id: publisher
+
+ // The web channel this publisher works on.
+ property var webChannel
+
/**
* This map contains the registered objects indexed by their name.
*/
property variant registeredObjects
+ property var subscriberCountMap
+
+ // Map of object names to maps of signal names to an array of all their properties.
+ // The last value is an array as a signal can be the notify signal of multiple properties.
+ property var signalToPropertyMap
+
+ // Objects that changed their properties and are waiting for idle client.
+ // map of object name to map of signal name to arguments
+ property var pendingPropertyUpdates
+
+ // true when the client is idle, false otherwise
+ property bool clientIsIdle: false
+
+ // true when no property updates should be sent, false otherwise
+ property bool blockUpdates: false
+
+ // true when at least one client needs to be initialized,
+ // i.e. when a Qt.init came in which was not handled yet.
+ property bool pendingInit: false
+
+ // true when at least one client was initialized and thus
+ // the property updates have been initialized and the
+ // object info map set.
+ property bool propertyUpdatesInitialized: false
+
+ function convertQMLArgsToJSArgs(qmlArgs)
+ {
+ // NOTE: QML arguments is a map not an array it seems...
+ // so do the conversion manually
+ var args = [];
+ for (var i = 0; i < qmlArgs.length; ++i) {
+ args.push(qmlArgs[i]);
+ }
+ return args;
+ }
/**
* Handle the given WebChannel client request and potentially give a response.
*
* @return true if the request was handled, false otherwise.
*/
- function handleRequest(data, webChannel)
+ function handleRequest(data)
{
var message = typeof(data) === "string" ? JSON.parse(data) : data;
if (!message.data) {
@@ -64,60 +104,204 @@ MetaObjectPublisherImpl
if (!payload.type) {
return false;
}
- var object = payload.object ? registeredObjects[payload.object] : null;
-
- if (payload.type === "Qt.invokeMethod" && object) {
- var method = object[payload.method];
- webChannel.respond(message.id, method.apply(method, payload.args));
- } else if (payload.type === "Qt.connectToSignal" && object) {
- object[payload.signal].connect(function() {
- // NOTE: QML arguments is a map not an array it seems...
- // so do the conversion manually
- var args = [];
- for (var i = 0; i < arguments.length; ++i) {
- args.push(arguments[i]);
+
+ if (payload.object) {
+ var object = registeredObjects[payload.object];
+
+ if (payload.type === "Qt.invokeMethod") {
+ var method = object[payload.method];
+ if (method !== undefined) {
+ webChannel.respond(message.id, method.apply(method, payload.args));
+ return true;
}
- webChannel.sendMessage("Qt.signal", {
- object: payload.object,
- signal: payload.signal,
- args: args
- });
- });
- } else if (payload.type === "Qt.getProperty" && object) {
- webChannel.respond(message.id, object[payload.property]);
- } else if (payload.type === "Qt.setProperty" && object) {
- object[payload.property] = payload.value;
- } else if (payload.type === "Qt.getObjects") {
- webChannel.respond(message.id, registeredObjectInfos());
- } else if (payload.type === "Qt.Debug") {
+ return false;
+ }
+ if (payload.type === "Qt.connectToSignal") {
+ if (object.hasOwnProperty(payload.signal)) {
+ subscriberCountMap = subscriberCountMap || {};
+ subscriberCountMap[payload.object] = subscriberCountMap[payload.object] || {};
+
+ // if no one is connected, connect.
+ if (!subscriberCountMap[payload.object].hasOwnProperty(payload.signal)) {
+ object[payload.signal].connect(function() {
+ var args = convertQMLArgsToJSArgs(arguments);
+ webChannel.sendMessage("Qt.signal", {
+ object: payload.object,
+ signal: payload.signal,
+ args: args
+ });
+ });
+ subscriberCountMap[payload.object][payload.signal] = true;
+ }
+ return true;
+ }
+ return false;
+ }
+ if (payload.type === "Qt.setProperty") {
+ object[payload.property] = payload.value;
+ return true;
+ }
+ }
+ if (payload.type === "Qt.idle") {
+ clientIsIdle = true;
+ return true;
+ }
+ if (payload.type === "Qt.init") {
+ if (!blockUpdates) {
+ initializeClients();
+ } else {
+ pendingInit = true;
+ }
+ return true;
+ }
+ if (payload.type === "Qt.Debug") {
console.log("DEBUG: ", payload.message);
- } else {
- return false;
+ return true;
}
-
- return true;
+ return false;
}
function registerObjects(objects)
{
+ if (propertyUpdatesInitialized) {
+ console.error("Registered new object after initialization. This does not work!");
+ }
// joining a JS map and a QML one is not as easy as one would assume...
- for (var name in registeredObjects) {
- if (!objects[name]) {
- objects[name] = registeredObjects[name];
+ // NOTE: the extra indirection via "merged" is required, using registeredObjects directly
+ // does not work! this looks like a QML/v8 bug to me, but I could not find a
+ // standalone testcase which reproduces this behavior :(
+ var merged = registeredObjects;
+ for (var name in objects) {
+ if (!merged.hasOwnProperty(name)) {
+ merged[name] = objects[name];
+ }
+ }
+ registeredObjects = merged;
+ }
+
+ function initializeClients()
+ {
+ var objectInfos = classInfoForObjects(registeredObjects);
+ webChannel.sendMessage("Qt.init", objectInfos);
+ if (!propertyUpdatesInitialized) {
+ for (var objectName in objectInfos) {
+ var objectInfo = objectInfos[objectName];
+ var object = registeredObjects[objectName];
+ initializePropertyUpdates(objectName, objectInfo, object);
}
+ propertyUpdatesInitialized = true;
}
- registeredObjects = objects;
+ pendingInit = false;
}
- function registeredObjectInfos()
+ // This function goes through all properties of all objects and connects against
+ // their notify signal.
+ // When receiving a notify signal, it will send a Qt.propertyUpdate message to the
+ // server.
+ function initializePropertyUpdates(objectName, objectInfo, object)
{
- var objectInfos = {};
- for (var name in registeredObjects) {
- var object = registeredObjects[name];
- if (object) {
- objectInfos[name] = classInfoForObject(object);
+ for (var propertyIndex in objectInfo.properties) {
+ var propertyInfo = objectInfo.properties[propertyIndex];
+ var propertyName = propertyInfo[0];
+ var signalName = propertyInfo[1];
+
+ if (!signalName) // Property without NOTIFY signal
+ continue;
+
+ if (signalName === 1) {
+ /// signal name is optimized away, reconstruct the actual name
+ signalName = propertyName + "Changed";
+ }
+
+ signalToPropertyMap[objectName] = signalToPropertyMap[objectName] || {};
+ signalToPropertyMap[objectName][signalName] = signalToPropertyMap[objectName][signalName] || [];
+ var connectedProperties = signalToPropertyMap[objectName][signalName];
+ var numConnectedProperties = connectedProperties === undefined ? 0 : connectedProperties.length;
+
+ // Only connect for a property update once
+ if (numConnectedProperties === 0) {
+ (function(signalName) {
+ object[signalName].connect(function() {
+ pendingPropertyUpdates[objectName] = pendingPropertyUpdates[objectName] || {};
+ pendingPropertyUpdates[objectName][signalName] = arguments;
+ });
+ })(signalName);
+ }
+
+ if (connectedProperties.indexOf(propertyName) === -1) {
+ /// TODO: this ensures that a given property is only once in
+ /// the list of connected properties.
+ /// This happens when multiple clients are connected to
+ /// a single webchannel. A better place for the initialization
+ /// should be found.
+ connectedProperties.push(propertyName);
}
}
- return objectInfos;
+ }
+
+ function sendPendingPropertyUpdates()
+ {
+ if (blockUpdates || !clientIsIdle) {
+ return;
+ }
+
+ var data = [];
+ for (var objectName in pendingPropertyUpdates) {
+ var object = registeredObjects[objectName];
+ var signals = pendingPropertyUpdates[objectName];
+ var propertyMap = {};
+ for (var signalName in signals) {
+ var propertyList = signalToPropertyMap[objectName][signalName];
+ for (var propertyIndex in propertyList) {
+ var propertyName = propertyList[propertyIndex];
+ var propertyValue = object[propertyName];
+ propertyMap[propertyName] = propertyValue;
+ }
+ signals[signalName] = convertQMLArgsToJSArgs(signals[signalName]);
+ }
+ data.push({
+ object: objectName,
+ signals: signals,
+ propertyMap: propertyMap
+ });
+ }
+ pendingPropertyUpdates = {};
+ if (data.length > 0) {
+ webChannel.sendMessage("Qt.propertyUpdate", data);
+ clientIsIdle = false;
+ }
+ }
+
+ Component.onCompleted: {
+ // Initializing this in the property declaration is not possible and yields to "undefined"
+ signalToPropertyMap = {}
+ pendingPropertyUpdates = {}
+ registeredObjects = {}
+ }
+
+ onBlockUpdatesChanged: {
+ if (blockUpdates) {
+ return;
+ }
+
+ if (pendingInit) {
+ initializeClients();
+ } else {
+ sendPendingPropertyUpdates();
+ }
+ }
+
+ /**
+ * 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.
+ */
+ Timer {
+ id: propertyUpdateTimer
+ /// TODO: what is the proper value here?
+ interval: 50;
+ running: !publisher.blockUpdates && publisher.clientIsIdle;
+ repeat: true;
+ onTriggered: publisher.sendPendingPropertyUpdates()
}
}
diff --git a/src/qobject.js b/src/qobject.js
index d221525..012332e 100644
--- a/src/qobject.js
+++ b/src/qobject.js
@@ -42,17 +42,78 @@
function QObject(name, data, webChannel)
{
this.__id__ = name;
+ webChannel.objectMap[name] = this;
+
+ // List of callbacks that get invoked upon signal emission
this.__objectSignals__ = {};
- var methodsAndSignals = [];
- for (var i in data.methods)
- methodsAndSignals.push(data.methods[i]);
- for (var i in data.signals)
- methodsAndSignals.push(data.signals[i]);
+ // Cache of all properties, updated when a notify signal is emitted
+ this.__propertyCache__ = {};
var object = this;
- methodsAndSignals.forEach(function(method) {
+ // ----------------------------------------------------------------------
+
+ function addSignal(signal, isPropertyNotifySignal)
+ {
+ object[signal] = {
+ connect: function(callback) {
+ if (typeof(callback) !== "function") {
+ console.error("Bad callback given to connect to signal " + signal);
+ return;
+ }
+
+ object.__objectSignals__[signal] = object.__objectSignals__[signal] || [];
+ object.__objectSignals__[signal].push(callback);
+
+ if (!isPropertyNotifySignal) {
+ // only required for "pure" signals, handled separately for properties in propertyUpdate
+ webChannel.exec({
+ type: "Qt.connectToSignal",
+ object: object.__id__,
+ signal: signal
+ });
+ }
+ }
+ // TODO: disconnect eventually
+ };
+ }
+
+ /**
+ * Invokes all callbacks for the given signalname. Also works for property notify callbacks.
+ */
+ function invokeSignalCallbacks(signalName, signalArgs)
+ {
+ var connections = object.__objectSignals__[signalName];
+ if (connections) {
+ connections.forEach(function(callback) {
+ callback.apply(callback, signalArgs);
+ });
+ }
+ }
+
+ this.propertyUpdate = function(signals, propertyMap)
+ {
+ // update property cache
+ for (var propertyName in propertyMap) {
+ var propertyValue = propertyMap[propertyName];
+ object.__propertyCache__[propertyName] = propertyValue;
+ }
+
+ for (var signalName in signals) {
+ // Invoke all callbacks, as signalEmitted() does not. This ensures the
+ // property cache is updated before the callbacks are invoked.
+ invokeSignalCallbacks(signalName, signals[signalName]);
+ }
+ }
+
+ this.signalEmitted = function(signalName, signalArgs)
+ {
+ invokeSignalCallbacks(signalName, signalArgs);
+ }
+
+ function addMethod(method)
+ {
object[method] = function() {
var args = [];
var callback;
@@ -64,80 +125,123 @@ function QObject(name, data, webChannel)
}
webChannel.exec({"type": "Qt.invokeMethod", "object": object.__id__, "method": method, "args": args}, function(response) {
- if ((response != undefined) && callback) {
+ if ( (response !== undefined) && callback ) {
(callback)(response);
}
});
};
- });
+ }
- function connectToSignal(signal)
+ function bindGetterSetter(propertyInfo)
{
- object[signal].connect = function(callback) {
- if (typeof(callback) !== "function") {
- console.error("Bad callback given to connect to signal " + signal);
+ var propertyName = propertyInfo[0];
+ var notifySignal = propertyInfo[1];
+ // initialize property cache with current value
+ object.__propertyCache__[propertyName] = propertyInfo[2]
+
+ if (notifySignal) {
+ if (notifySignal === 1) {
+ /// signal name is optimized away, reconstruct the actual name
+ notifySignal = propertyName + "Changed";
+ }
+ addSignal(notifySignal, true);
+ }
+
+ object.__defineSetter__(propertyName, function(value) {
+ if (value === undefined) {
+ console.warn("Property setter for " + propertyName + " called with undefined value!");
return;
}
- object.__objectSignals__[signal] = object.__objectSignals__[signal] || [];
- webChannel.exec({"type": "Qt.connectToSignal", "object": object.__id__, "signal": signal});
- object.__objectSignals__[signal].push(callback);
- };
- }
- for (var i in data.signals) {
- var signal = data.signals[i];
- connectToSignal(data.signals[i]);
- }
+ object.__propertyCache__[propertyName] = value;
+ webChannel.exec({"type": "Qt.setProperty", "object": object.__id__, "property": propertyName, "value": value });
- function bindGetterSetter(property)
- {
- object.__defineSetter__(property, function(value) {
- webChannel.exec({"type": "Qt.setProperty", "object": object.__id__, "property": property, "value": value });
});
- object.__defineGetter__(property, function() {
- return (function(callback) {
- webChannel.exec({"type": "Qt.getProperty", "object": object.__id__, "property": property}, function(response) {
+ object.__defineGetter__(propertyName, function () {
+ return (function (callback) {
+ var propertyValue = object.__propertyCache__[propertyName];
+ if (propertyValue === undefined) {
+ // This shouldn't happen
+ console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__);
+ }
+
+ // TODO: A callback is not required here anymore, but is kept for backwards compatibility
+ if (callback !== undefined) {
if (typeof(callback) !== "function") {
console.error("Bad callback given to get property " + property);
return;
}
- callback(response);
- });
+ callback(propertyValue);
+ } else {
+ return propertyValue;
+ }
});
});
}
- for (i in data.properties) {
- bindGetterSetter(data.properties[i]);
- }
- for (i in data.enums) {
- object[i] = data.enums[i];
+ // ----------------------------------------------------------------------
+
+ data.methods.forEach(addMethod);
+
+ data.properties.forEach(bindGetterSetter);
+
+ data.signals.forEach(function(signal) { addSignal(signal, false); });
+
+ for (var name in data.enums) {
+ object[name] = data.enums[name];
}
}
window.setupQObjectWebChannel = function(webChannel, doneCallback)
{
+ // prevent multiple initialization which might happen with multiple webchannel clients.
+ var initialized = false;
+
webChannel.subscribe(
"Qt.signal",
function(payload) {
- var object = window[payload.object];
+ var object = webChannel.objectMap[payload.object];
if (object) {
- var connections = object.__objectSignals__[payload.signal];
- if (connections) {
- connections.forEach(function(callback) {
- callback.apply(callback, payload.args);
- });
- }
+ object.signalEmitted(payload.signal, payload.args);
+ } else {
+ console.warn("Unhandled signal: " + payload.object + "::" + payload.signal);
}
}
);
- webChannel.exec({type:"Qt.getObjects"}, function(payload) {
- for (var objectName in payload) {
- var data = payload[objectName];
- var object = new QObject(objectName, data, webChannel);
- window[objectName] = object;
+
+ webChannel.subscribe(
+ "Qt.propertyUpdate",
+ function(payload) {
+ for (var i in payload) {
+ var data = payload[i];
+ var object = webChannel.objectMap[data.object];
+ if (object) {
+ object.propertyUpdate(data.signals, data.propertyMap);
+ } else {
+ console.warn("Unhandled property update: " + data.object + "::" + data.signal);
+ }
+ }
+ setTimeout(function() { webChannel.exec({type: "Qt.idle"}); }, 0);
}
- if (doneCallback) {
- doneCallback();
+ );
+
+ webChannel.subscribe(
+ "Qt.init",
+ function(payload) {
+ if (initialized) {
+ return;
+ }
+ initialized = true;
+ for (var objectName in payload) {
+ var data = payload[objectName];
+ var object = new QObject(objectName, data, webChannel);
+ window[objectName] = object;
+ }
+ if (doneCallback) {
+ doneCallback();
+ }
+ setTimeout(function() { webChannel.exec({type: "Qt.idle"}); }, 0);
}
- });
+ );
+
+ webChannel.exec({type:"Qt.init"});
};
diff --git a/src/qtmetaobjectpublisher.cpp b/src/qtmetaobjectpublisher.cpp
index 1646434..8e871e0 100644
--- a/src/qtmetaobjectpublisher.cpp
+++ b/src/qtmetaobjectpublisher.cpp
@@ -45,11 +45,33 @@
#include <QMetaObject>
#include <QMetaProperty>
-QtMetaObjectPublisher::QtMetaObjectPublisher(QObject *parent)
- : QObject(parent)
+static const QString KEY_SIGNALS = QStringLiteral("signals");
+static const QString KEY_METHODS = QStringLiteral("methods");
+static const QString KEY_PROPERTIES = QStringLiteral("properties");
+static const QString KEY_ENUMS = QStringLiteral("enums");
+
+QtMetaObjectPublisher::QtMetaObjectPublisher(QQuickItem *parent)
+ : QQuickItem(parent)
{
}
+QVariantMap QtMetaObjectPublisher::classInfoForObjects(const QVariantMap &objectMap) const
+{
+ QVariantMap ret;
+ QMap<QString, QVariant>::const_iterator it = objectMap.constBegin();
+ while (it != objectMap.constEnd()) {
+ QObject* object = it.value().value<QObject*>();
+ if (object) {
+ const QVariantMap &info = classInfoForObject(object);
+ if (!info.isEmpty()) {
+ ret[it.key()] = info;
+ }
+ }
+ ++it;
+ }
+ return ret;
+}
+
QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const
{
QVariantMap data;
@@ -57,19 +79,54 @@ QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const
qWarning("null object given to MetaObjectPublisher - bad API usage?");
return data;
}
- QStringList qtSignals, qtMethods, qtProperties;
+ QVariantList qtSignals, qtMethods;
+ QVariantList qtProperties;
QVariantMap qtEnums;
const QMetaObject* metaObject = object->metaObject();
- for (int i = 0; i < metaObject->propertyCount(); ++i)
- qtProperties.append(metaObject->property(i).name());
+ QSet<int> notifySignals;
+ QSet<QString> properties;
+ for (int i = 0; i < metaObject->propertyCount(); ++i) {
+ const QMetaProperty &prop = metaObject->property(i);
+ QVariantList propertyInfo;
+ const QString &propertyName = QString::fromLatin1(prop.name());
+ propertyInfo.append(propertyName);
+ properties << propertyName;
+ if (prop.hasNotifySignal()) {
+ notifySignals << prop.notifySignalIndex();
+ const int numParams = prop.notifySignal().parameterCount();
+ if (numParams > 1) {
+ qWarning("Notify signal for property '%s' has %d parameters, expected zero or one.",
+ prop.name(), numParams);
+ }
+ propertyInfo.append(QString::fromLatin1(prop.notifySignal().name()));
+ } else {
+ if (!prop.isConstant()) {
+ qWarning("Property '%s'' of object '%s' has no notify signal and is not constant, "
+ "value updates in HTML will be broken!",
+ prop.name(), object->metaObject()->className());
+ }
+ propertyInfo.append(QString());
+ }
+ propertyInfo.append(prop.read(object));
+ qtProperties.append(QVariant::fromValue(propertyInfo));
+ }
for (int i = 0; i < metaObject->methodCount(); ++i) {
- QMetaMethod method = metaObject->method(i);
- QString signature = method.methodSignature();
- QString name = signature.left(signature.indexOf("("));
+ if (notifySignals.contains(i)) {
+ continue;
+ }
+ const QMetaMethod &method = metaObject->method(i);
+ //NOTE: This will not work for overloaded methods/signals.
+ //NOTE: this must be a string, otherwise it will be converted to '{}' in QML
+ const QString &name = QString::fromLatin1(method.name());
+ if (properties.contains(name)) {
+ // optimize: Don't send the getter method, it gets overwritten by the
+ // property on the client side anyways.
+ continue;
+ }
if (method.access() == QMetaMethod::Public)
- qtMethods << signature << name;
+ qtMethods << name;
if (method.methodType() == QMetaMethod::Signal)
- qtSignals << signature << name;
+ qtSignals << name;
}
for (int i = 0; i < metaObject->enumeratorCount(); ++i) {
QMetaEnum enumerator = metaObject->enumerator(i);
@@ -79,9 +136,9 @@ QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const
}
qtEnums[enumerator.name()] = values;
}
- data["signals"] = qtSignals;
- data["methods"] = qtMethods;
- data["properties"] = qtProperties;
- data["enums"] = qtEnums;
+ data[KEY_SIGNALS] = qtSignals;
+ data[KEY_METHODS] = qtMethods;
+ data[KEY_PROPERTIES] = QVariant::fromValue(qtProperties);
+ data[KEY_ENUMS] = qtEnums;
return data;
}
diff --git a/src/qtmetaobjectpublisher.h b/src/qtmetaobjectpublisher.h
index 573d120..0c19374 100644
--- a/src/qtmetaobjectpublisher.h
+++ b/src/qtmetaobjectpublisher.h
@@ -44,15 +44,19 @@
#include <QObject>
#include <QVariantMap>
+#include <QQuickItem>
-class QtMetaObjectPublisher : public QObject
+class QObjectWrapper;
+
+// NOTE: QQuickItem inheritance required to enable QML item nesting (i.e. Timer in MetaObjectPublisher)
+class QtMetaObjectPublisher : public QQuickItem
{
Q_OBJECT
public:
- explicit QtMetaObjectPublisher(QObject *parent = 0);
+ explicit QtMetaObjectPublisher(QQuickItem *parent = 0);
+ Q_INVOKABLE QVariantMap classInfoForObjects(const QVariantMap &objects) const;
Q_INVOKABLE QVariantMap classInfoForObject(QObject *object) const;
};
#endif // QTMETAOBJECTPUBLISHER_H
-
diff --git a/src/src.pro b/src/src.pro
index 8792e04..ee77e4b 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -3,7 +3,7 @@ include(src.pri)
TEMPLATE = lib
TARGET = qwebchannel
TARGETPATH = Qt/labs/WebChannel
-QT += qml
+QT += qml quick
CONFIG += qt plugin
TARGET = $$qtLibraryTarget($$TARGET)
diff --git a/src/webchannel.js b/src/webchannel.js
index 6141268..1e6603c 100644
--- a/src/webchannel.js
+++ b/src/webchannel.js
@@ -123,4 +123,6 @@ var QWebChannel = function(baseUrl, initCallback)
{
channel.send({"data" : {"type" : "Qt.Debug", "message" : message}});
};
+
+ this.objectMap = {};
};