summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMilian Wolff <milian.wolff@kdab.com>2019-03-22 11:03:43 +0100
committerMilian Wolff <milian.wolff@kdab.com>2019-03-25 14:07:36 +0000
commit7a673eb1a6902ef6f2eb03d6beab139282b1e4a5 (patch)
treeaaa3e1c31152327b9662bee7a00c0249b916eb66
parent857cfc1adecd72750cea26d4c91371f4aaf9a68f (diff)
downloadqtwebchannel-7a673eb1a6902ef6f2eb03d6beab139282b1e4a5.tar.gz
Publish overloaded methods and signals to JavaScript
Previously, we only published the first method or signal of any given name. We keep this behavior for the nice JavaScript notation that looks like a normal JavaScript method call `foo.bar(...)`. When you need to call a different overloaded method, this patch offers you to specify the explicit signature on the JavaScript side. I.e. when we have an object with `foo(int i)` and `foo(const QString &str, int i)`, then on the JavaScript a call to `obj.foo(...)` will always call the first method like before. But now you can specify the full QMetaMethod signature and call matching methods explicitly via `obj["foo(int)"]` or `obj["foo(QString,int)"]`. Automatic overload resolution on the C++ side for the nice notation cannot easily be implemented: We need to know the return value of the called function, otherwise we cannot construct a valid QGenericReturnArgument. Furthermore, we wouldn't be able to differentiate between e.g. any numeric types on the C++ side, since JavaScript only has a single `double` type internally. [ChangeLog][QWebChannel][General] It is now possible to explicitly call overloaded methods or connect to overloaded signals by specifying the full method or signal signature in string form on the JavaScript side. Fixes: QTBUG-73010 Change-Id: I4645edee97af56fd8d126e77d70dc33ed3513deb Reviewed-by: Arno Rehn <a.rehn@menlosystems.com> Reviewed-by: Frederik Gladhorn <frederik.gladhorn@qt.io> Reviewed-by: Milian Wolff <milian.wolff@kdab.com>
-rw-r--r--examples/webchannel/shared/qwebchannel.js2
-rw-r--r--src/webchannel/doc/src/javascript.qdoc40
-rw-r--r--src/webchannel/qmetaobjectpublisher.cpp29
-rw-r--r--tests/auto/qml/testobject.cpp18
-rw-r--r--tests/auto/qml/testobject.h8
-rw-r--r--tests/auto/qml/tst_webchannel.qml60
-rw-r--r--tests/auto/webchannel/tst_webchannel.cpp146
-rw-r--r--tests/auto/webchannel/tst_webchannel.h11
8 files changed, 226 insertions, 88 deletions
diff --git a/examples/webchannel/shared/qwebchannel.js b/examples/webchannel/shared/qwebchannel.js
index 1e0d72a..800a66e 100644
--- a/examples/webchannel/shared/qwebchannel.js
+++ b/examples/webchannel/shared/qwebchannel.js
@@ -266,7 +266,7 @@ function QObject(name, data, webChannel)
return;
// also note that we always get notified about the destroyed signal
- if (signalName === "destroyed")
+ if (signalName === "destroyed" || signalName === "destroyed()" || signalName === "destroyed(QObject*)")
return;
// and otherwise we only need to be connected only once
diff --git a/src/webchannel/doc/src/javascript.qdoc b/src/webchannel/doc/src/javascript.qdoc
index e643034..9f8c580 100644
--- a/src/webchannel/doc/src/javascript.qdoc
+++ b/src/webchannel/doc/src/javascript.qdoc
@@ -97,4 +97,44 @@ new QWebChannel(yourTransport, function(channel) {
console.log(foo.MyEnum.MyEnumerator);
});
\endcode
+
+ \section2 Overloaded methods and signals
+
+ When you publish a \c QObject that has overloaded methods or signals, then
+ only the first one is accessible directly via the pretty JavaScript notation.
+ All others are accessible through their complete \c QMetaMethod signature.
+ Assume we have the following \c QObject subclass on the C++ side:
+
+ \code
+ class Foo : public QObject
+ {
+ Q_OBJECT
+ slots:
+ void foo(int i);
+ void foo(const QString &str);
+ void foo(const QString &str, int i);
+
+ signals:
+ void bar(int i);
+ void bar(const QString &str);
+ void bar(const QString &str, int i);
+ };
+ \endcode
+
+ Then you can interact with this class on the JavaScript side like this:
+
+ \code
+ // methods
+ foo.foo(42); // will call first method named foo, i.e. foo(int i)
+ foo.foo("asdf"); // will also call foo(int i), probably not what you want
+ foo["foo(int)"](42); // explicitly call foo(int i)
+ foo["foo(QString)"]("asdf"); // explicitly call foo(const QString &str)
+ foo["foo(QString,int)"]("asdf", 42); // explicitly call foo(const QString &str, int i)
+
+ // signals
+ foo.bar.connect(...); // connect to first signal named bar, i.e. bar(int i)
+ foo["bar(int)"].connect(...); // connect explicitly to bar(int i)
+ foo["bar(QString)"].connect(...); // connect explicitly to bar(const QString &str)
+ foo["bar(QString,int)"].connect(...); // connect explicitly to bar(const QString &str, int i)
+ \endcode
*/
diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp
index 3c96126..3c1d030 100644
--- a/src/webchannel/qmetaobjectpublisher.cpp
+++ b/src/webchannel/qmetaobjectpublisher.cpp
@@ -196,19 +196,13 @@ QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object, QWeb
propertyInfo.append(wrapResult(prop.read(object), transport));
qtProperties.append(propertyInfo);
}
- for (int i = 0; i < metaObject->methodCount(); ++i) {
- if (notifySignals.contains(i)) {
- continue;
- }
- const QMetaMethod &method = metaObject->method(i);
- //NOTE: this must be a string, otherwise it will be converted to '{}' in QML
- const QString &name = QString::fromLatin1(method.name());
- // optimize: skip overloaded methods/signals or property getters, on the JS side we can only
- // call one of them anyways
- // TODO: basic support for overloaded signals, methods
- if (identifiers.contains(name)) {
- continue;
- }
+ auto addMethod = [&qtSignals, &qtMethods, &identifiers](int i, const QMetaMethod &method, const QByteArray &rawName) {
+ //NOTE: the name must be a string, otherwise it will be converted to '{}' in QML
+ const auto name = QString::fromLatin1(rawName);
+ // only the first method gets called with its name directly
+ // others must be called by explicitly passing the method signature
+ if (identifiers.contains(name))
+ return;
identifiers << name;
// send data as array to client with format: [name, index]
QJsonArray data;
@@ -219,6 +213,15 @@ QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object, QWeb
} else if (method.access() == QMetaMethod::Public) {
qtMethods.append(data);
}
+ };
+ for (int i = 0; i < metaObject->methodCount(); ++i) {
+ if (notifySignals.contains(i)) {
+ continue;
+ }
+ const QMetaMethod &method = metaObject->method(i);
+ addMethod(i, method, method.name());
+ // for overload resolution also pass full method signature
+ addMethod(i, method, method.methodSignature());
}
for (int i = 0; i < metaObject->enumeratorCount(); ++i) {
QMetaEnum enumerator = metaObject->enumerator(i);
diff --git a/tests/auto/qml/testobject.cpp b/tests/auto/qml/testobject.cpp
index 4968bf4..ad302e7 100644
--- a/tests/auto/qml/testobject.cpp
+++ b/tests/auto/qml/testobject.cpp
@@ -59,4 +59,22 @@ void TestObject::triggerSignals()
emit testSignalInt(0);
}
+int TestObject::testOverload(int i)
+{
+ emit testOverloadSignal(i);
+ return i + 1;
+}
+
+QString TestObject::testOverload(const QString &str)
+{
+ emit testOverloadSignal(str);
+ return str.toUpper();
+}
+
+QString TestObject::testOverload(const QString &str, int i)
+{
+ emit testOverloadSignal(str, i);
+ return str.toUpper() + QString::number(i + 1);
+}
+
QT_END_NAMESPACE
diff --git a/tests/auto/qml/testobject.h b/tests/auto/qml/testobject.h
index 5813dae..b9c5ecc 100644
--- a/tests/auto/qml/testobject.h
+++ b/tests/auto/qml/testobject.h
@@ -48,10 +48,18 @@ public:
public slots:
void triggerSignals();
+ int testOverload(int i);
+ QString testOverload(const QString &str);
+ QString testOverload(const QString &str, int i);
+
signals:
void testSignalBool(bool testBool);
void testSignalInt(int testInt);
+ void testOverloadSignal(int i);
+ void testOverloadSignal(const QString &str);
+ void testOverloadSignal(const QString &str, int i);
+
private:
QObject *embeddedObject;
};
diff --git a/tests/auto/qml/tst_webchannel.qml b/tests/auto/qml/tst_webchannel.qml
index 0069261..87ce84b 100644
--- a/tests/auto/qml/tst_webchannel.qml
+++ b/tests/auto/qml/tst_webchannel.qml
@@ -476,4 +476,64 @@ TestCase {
compare(signalArgs, [42, 42, 1, 1, 0, 0]);
}
+
+ function test_overloading()
+ {
+ var signalArgs_implicit = [];
+ var signalArgs_explicit1 = [];
+ var signalArgs_explicit2 = [];
+ var signalArgs_explicit3 = [];
+ function logSignalArgs(container) {
+ return function(...args) {
+ container.push(args);
+ };
+ }
+ var returnValues = [];
+ function logReturnValue(value) {
+ returnValues.push(value);
+ }
+ var channel = client.createChannel(function(channel) {
+ var testObject = channel.objects.testObject;
+ testObject.testOverloadSignal.connect(logSignalArgs(signalArgs_implicit));
+ testObject["testOverloadSignal(int)"].connect(logSignalArgs(signalArgs_explicit1));
+ testObject["testOverloadSignal(QString)"].connect(logSignalArgs(signalArgs_explicit2));
+ testObject["testOverloadSignal(QString,int)"].connect(logSignalArgs(signalArgs_explicit3));
+ testObject.testOverload(99, logReturnValue);
+ testObject["testOverload(int)"](41, logReturnValue);
+ testObject["testOverload(QString)"]("hello world", logReturnValue);
+ testObject["testOverload(QString,int)"]("the answer is ", 41, logReturnValue);
+ });
+ client.awaitInit();
+
+ function awaitMessage(type)
+ {
+ var msg = client.awaitMessage();
+ compare(msg.type, type);
+ compare(msg.object, "testObject");
+ }
+
+ console.log("sig1");
+ awaitMessage(JSClient.QWebChannelMessageTypes.connectToSignal);
+ console.log("sig2");
+ awaitMessage(JSClient.QWebChannelMessageTypes.connectToSignal);
+ console.log("sig3");
+ awaitMessage(JSClient.QWebChannelMessageTypes.connectToSignal);
+
+ console.log("method1");
+ awaitMessage(JSClient.QWebChannelMessageTypes.invokeMethod);
+ console.log("method2");
+ awaitMessage(JSClient.QWebChannelMessageTypes.invokeMethod);
+ console.log("method3");
+ awaitMessage(JSClient.QWebChannelMessageTypes.invokeMethod);
+ console.log("method4");
+ awaitMessage(JSClient.QWebChannelMessageTypes.invokeMethod);
+
+ client.awaitIdle();
+
+ compare(signalArgs_implicit, [[99], [41]]);
+ compare(signalArgs_explicit1, signalArgs_implicit);
+ compare(signalArgs_explicit2, [["hello world"]]);
+ compare(signalArgs_explicit3, [["the answer is ", 41]]);
+ compare(returnValues, [100, 42, "HELLO WORLD", "THE ANSWER IS 42"]);
+ }
}
diff --git a/tests/auto/webchannel/tst_webchannel.cpp b/tests/auto/webchannel/tst_webchannel.cpp
index 9a0f575..790e5ac 100644
--- a/tests/auto/webchannel/tst_webchannel.cpp
+++ b/tests/auto/webchannel/tst_webchannel.cpp
@@ -275,6 +275,21 @@ void TestWebChannel::setJsonArray(const QJsonArray& v)
emit lastJsonArrayChanged();
}
+int TestWebChannel::readOverload(int i)
+{
+ return i + 1;
+}
+
+QString TestWebChannel::readOverload(const QString &arg)
+{
+ return arg.toUpper();
+}
+
+QString TestWebChannel::readOverload(const QString &arg, int i)
+{
+ return arg.toUpper() + QString::number(i + 1);
+}
+
void TestWebChannel::testRegisterObjects()
{
QWebChannel channel;
@@ -351,85 +366,39 @@ void TestWebChannel::testInfoForObject()
QCOMPARE(info["enums"].toObject(), expected);
}
+ QJsonArray expected;
+ auto addMethod = [&expected, &obj](const QString &name, const char *signature, bool addName = true) {
+ const auto index = obj.metaObject()->indexOfMethod(signature);
+ QVERIFY2(index != -1, signature);
+ if (addName)
+ expected.append(QJsonArray{name, index});
+ expected.append(QJsonArray{QString::fromUtf8(signature), index});
+ };
{ // methods & slots
- QJsonArray expected;
- {
- QJsonArray method;
- method.append(QStringLiteral("deleteLater"));
- method.append(obj.metaObject()->indexOfMethod("deleteLater()"));
- expected.append(method);
- }
- {
- QJsonArray method;
- method.append(QStringLiteral("slot1"));
- method.append(obj.metaObject()->indexOfMethod("slot1()"));
- expected.append(method);
- }
- {
- QJsonArray method;
- method.append(QStringLiteral("slot2"));
- method.append(obj.metaObject()->indexOfMethod("slot2(QString)"));
- expected.append(method);
- }
- {
- QJsonArray method;
- method.append(QStringLiteral("setReturnedObject"));
- method.append(obj.metaObject()->indexOfMethod("setReturnedObject(TestObject*)"));
- expected.append(method);
- }
- {
- QJsonArray method;
- method.append(QStringLiteral("setObjectProperty"));
- method.append(obj.metaObject()->indexOfMethod("setObjectProperty(QObject*)"));
- expected.append(method);
- }
- {
- QJsonArray method;
- method.append(QStringLiteral("setProp"));
- method.append(obj.metaObject()->indexOfMethod("setProp(QString)"));
- expected.append(method);
- }
- {
- QJsonArray method;
- method.append(QStringLiteral("fire"));
- method.append(obj.metaObject()->indexOfMethod("fire()"));
- expected.append(method);
- }
- {
- QJsonArray method;
- method.append(QStringLiteral("method1"));
- method.append(obj.metaObject()->indexOfMethod("method1()"));
- expected.append(method);
- }
+ expected = {};
+ addMethod(QStringLiteral("deleteLater"), "deleteLater()");
+ addMethod(QStringLiteral("slot1"), "slot1()");
+ addMethod(QStringLiteral("slot2"), "slot2(QString)");
+ addMethod(QStringLiteral("setReturnedObject"), "setReturnedObject(TestObject*)");
+ addMethod(QStringLiteral("setObjectProperty"), "setObjectProperty(QObject*)");
+ addMethod(QStringLiteral("setProp"), "setProp(QString)");
+ addMethod(QStringLiteral("fire"), "fire()");
+ addMethod(QStringLiteral("overload"), "overload(int)");
+ addMethod(QStringLiteral("overload"), "overload(QString)", false);
+ addMethod(QStringLiteral("overload"), "overload(QString,int)", false);
+ addMethod(QStringLiteral("method1"), "method1()");
QCOMPARE(info["methods"].toArray(), expected);
}
{ // signals
- QJsonArray expected;
- {
- QJsonArray signal;
- signal.append(QStringLiteral("destroyed"));
- signal.append(obj.metaObject()->indexOfMethod("destroyed(QObject*)"));
- expected.append(signal);
- }
- {
- QJsonArray signal;
- signal.append(QStringLiteral("sig1"));
- signal.append(obj.metaObject()->indexOfMethod("sig1()"));
- expected.append(signal);
- }
- {
- QJsonArray signal;
- signal.append(QStringLiteral("sig2"));
- signal.append(obj.metaObject()->indexOfMethod("sig2(QString)"));
- expected.append(signal);
- }
- {
- QJsonArray signal;
- signal.append(QStringLiteral("replay"));
- signal.append(obj.metaObject()->indexOfMethod("replay()"));
- expected.append(signal);
- }
+ expected = {};
+ addMethod(QStringLiteral("destroyed"), "destroyed(QObject*)");
+ addMethod(QStringLiteral("destroyed"), "destroyed()", false);
+ addMethod(QStringLiteral("sig1"), "sig1()");
+ addMethod(QStringLiteral("sig2"), "sig2(QString)");
+ addMethod(QStringLiteral("replay"), "replay()");
+ addMethod(QStringLiteral("overloadSignal"), "overloadSignal(int)");
+ addMethod(QStringLiteral("overloadSignal"), "overloadSignal(float)", false);
QCOMPARE(info["signals"].toArray(), expected);
}
@@ -618,6 +587,35 @@ void TestWebChannel::testInvokeMethodConversion()
}
}
+void TestWebChannel::testFunctionOverloading()
+{
+ QWebChannel channel;
+ channel.connectTo(m_dummyTransport);
+
+ // all method calls will use the first method's index
+ const auto method1 = metaObject()->indexOfMethod("readOverload(int)");
+ QVERIFY(method1 != -1);
+ const auto method2 = metaObject()->indexOfMethod("readOverload(QString)");
+ QVERIFY(method2 != -1);
+ QVERIFY(method1 < method2);
+ const auto method3 = metaObject()->indexOfMethod("readOverload(QString,int)");
+ QVERIFY(method3 != -1);
+ QVERIFY(method2 < method3);
+
+ { // int
+ const auto retVal = channel.d_func()->publisher->invokeMethod(this, method1, QJsonArray{1000});
+ QCOMPARE(retVal.toInt(), 1001);
+ }
+ { // QString
+ const auto retVal = channel.d_func()->publisher->invokeMethod(this, method2, QJsonArray{QStringLiteral("hello world")});
+ QCOMPARE(retVal.toString(), QStringLiteral("HELLO WORLD"));
+ }
+ { // QString, int
+ const auto retVal = channel.d_func()->publisher->invokeMethod(this, method3, QJsonArray{QStringLiteral("the answer is "), 41});
+ QCOMPARE(retVal.toString(), QStringLiteral("THE ANSWER IS 42"));
+ }
+}
+
void TestWebChannel::testSetPropertyConversion()
{
QWebChannel channel;
diff --git a/tests/auto/webchannel/tst_webchannel.h b/tests/auto/webchannel/tst_webchannel.h
index 3d16f7b..ed769e9 100644
--- a/tests/auto/webchannel/tst_webchannel.h
+++ b/tests/auto/webchannel/tst_webchannel.h
@@ -136,6 +136,8 @@ signals:
void returnedObjectChanged();
void propChanged(const QString&);
void replay();
+ void overloadSignal(int);
+ void overloadSignal(float);
public slots:
void slot1() {}
@@ -156,6 +158,10 @@ public slots:
void setProp(const QString&prop) {emit propChanged(mProp=prop);}
void fire() {emit replay();}
+ int overload(int i) { return i + 1; }
+ QString overload(const QString &str) { return str.toUpper(); }
+ QString overload(const QString &str, int i) { return str.toUpper() + QString::number(i + 1); }
+
protected slots:
void slot3() {}
@@ -293,6 +299,10 @@ public slots:
QJsonArray readJsonArray() const;
void setJsonArray(const QJsonArray &v);
+ int readOverload(int i);
+ QString readOverload(const QString &arg);
+ QString readOverload(const QString &arg, int i);
+
signals:
void lastIntChanged();
void lastBoolChanged();
@@ -308,6 +318,7 @@ private slots:
void testDeregisterObjectAtStart();
void testInfoForObject();
void testInvokeMethodConversion();
+ void testFunctionOverloading();
void testSetPropertyConversion();
void testDisconnect();
void testWrapRegisteredObject();