diff options
Diffstat (limited to 'user/qt5-qtwebchannel/kde-lts.patch')
-rw-r--r-- | user/qt5-qtwebchannel/kde-lts.patch | 576 |
1 files changed, 576 insertions, 0 deletions
diff --git a/user/qt5-qtwebchannel/kde-lts.patch b/user/qt5-qtwebchannel/kde-lts.patch new file mode 100644 index 000000000..352a4a906 --- /dev/null +++ b/user/qt5-qtwebchannel/kde-lts.patch @@ -0,0 +1,576 @@ +From adfc4bacdf18d9876a1ea908ce6aeb43555b0dbd Mon Sep 17 00:00:00 2001 +From: Milian Wolff <milian.wolff@kdab.com> +Date: Fri, 13 Dec 2019 18:07:26 +0100 +Subject: [PATCH 1/3] Handle signals in the registered object's thread + +Do not call `sender()` from a different thread. As the API documentation +indicates, that is not supported and can lead to crashes as experienced +on the CI frequently. + +Instead, we now have per-thread SignalHandlers and use those to get +notified about signals and metacall events. + +Moving a registered object into a different thread isn't supported once +it was registered. But the object can be deregistered, moved, and then +re-registered. + +[ChangeLog] Signals from objects living in a different thread than the +QWebChannel are now handled correctly. + +Task-number: QTBUG-51366 +Change-Id: I1edb0694b946a494b6c0d4a8a6dc6b452dcb2c7a +Reviewed-by: Arno Rehn <a.rehn@menlosystems.com> +Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> +(cherry picked from commit 28455e59c0b940200fe0223472a80104ce1a02dd) +--- + src/webchannel/qmetaobjectpublisher.cpp | 23 +++++++++++++++++------ + src/webchannel/qmetaobjectpublisher_p.h | 5 ++++- + src/webchannel/signalhandler_p.h | 3 +++ + tests/auto/webchannel/tst_webchannel.cpp | 4 +--- + 4 files changed, 25 insertions(+), 10 deletions(-) + +diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp +index 536eb5c..e997b75 100644 +--- a/src/webchannel/qmetaobjectpublisher.cpp ++++ b/src/webchannel/qmetaobjectpublisher.cpp +@@ -186,7 +186,6 @@ Q_DECLARE_TYPEINFO(OverloadResolutionCandidate, Q_MOVABLE_TYPE); + QMetaObjectPublisher::QMetaObjectPublisher(QWebChannel *webChannel) + : QObject(webChannel) + , webChannel(webChannel) +- , signalHandler(this) + , clientIsIdle(false) + , blockUpdates(false) + , propertyUpdatesInitialized(false) +@@ -333,6 +332,7 @@ QJsonObject QMetaObjectPublisher::initializeClient(QWebChannelAbstractTransport + + void QMetaObjectPublisher::initializePropertyUpdates(const QObject *const object, const QJsonObject &objectInfo) + { ++ auto *signalHandler = signalHandlerFor(object); + foreach (const QJsonValue &propertyInfoVar, objectInfo[KEY_PROPERTIES].toArray()) { + const QJsonArray &propertyInfo = propertyInfoVar.toArray(); + if (propertyInfo.size() < 2) { +@@ -353,14 +353,14 @@ void QMetaObjectPublisher::initializePropertyUpdates(const QObject *const object + + // Only connect for a property update once + if (connectedProperties.isEmpty()) { +- signalHandler.connectTo(object, signalIndex); ++ signalHandler->connectTo(object, signalIndex); + } + + connectedProperties.insert(propertyIndex); + } + + // also always connect to destroyed signal +- signalHandler.connectTo(object, s_destroyedSignalIndex); ++ signalHandler->connectTo(object, s_destroyedSignalIndex); + } + + void QMetaObjectPublisher::sendPendingPropertyUpdates() +@@ -590,7 +590,7 @@ void QMetaObjectPublisher::objectDestroyed(const QObject *object) + // only remove from handler when we initialized the property updates + // cf: https://bugreports.qt.io/browse/QTBUG-60250 + if (propertyUpdatesInitialized) { +- signalHandler.remove(object); ++ signalHandlerFor(object)->remove(object); + signalToPropertyMap.remove(object); + } + pendingPropertyUpdates.remove(object); +@@ -913,9 +913,9 @@ void QMetaObjectPublisher::handleMessage(const QJsonObject &message, QWebChannel + return; + transport->sendMessage(createResponse(message.value(KEY_ID), wrapResult(result, transport))); + } else if (type == TypeConnectToSignal) { +- signalHandler.connectTo(object, message.value(KEY_SIGNAL).toInt(-1)); ++ signalHandlerFor(object)->connectTo(object, message.value(KEY_SIGNAL).toInt(-1)); + } else if (type == TypeDisconnectFromSignal) { +- signalHandler.disconnectFrom(object, message.value(KEY_SIGNAL).toInt(-1)); ++ signalHandlerFor(object)->disconnectFrom(object, message.value(KEY_SIGNAL).toInt(-1)); + } else if (type == TypeSetProperty) { + setProperty(object, message.value(KEY_PROPERTY).toInt(-1), + message.value(KEY_VALUE)); +@@ -948,4 +948,15 @@ void QMetaObjectPublisher::timerEvent(QTimerEvent *event) + } + } + ++SignalHandler<QMetaObjectPublisher> *QMetaObjectPublisher::signalHandlerFor(const QObject *object) ++{ ++ auto thread = object->thread(); ++ auto it = signalHandlers.find(thread); ++ if (it == signalHandlers.end()) { ++ it = signalHandlers.emplace(thread, this).first; ++ it->second.moveToThread(thread); ++ } ++ return &it->second; ++} ++ + QT_END_NAMESPACE +diff --git a/src/webchannel/qmetaobjectpublisher_p.h b/src/webchannel/qmetaobjectpublisher_p.h +index bbd9875..ded0d33 100644 +--- a/src/webchannel/qmetaobjectpublisher_p.h ++++ b/src/webchannel/qmetaobjectpublisher_p.h +@@ -60,6 +60,8 @@ + #include <QPointer> + #include <QJsonObject> + ++#include <unordered_map> ++ + #include "qwebchannelglobal.h" + + QT_BEGIN_NAMESPACE +@@ -272,7 +274,8 @@ private: + friend class TestWebChannel; + + QWebChannel *webChannel; +- SignalHandler<QMetaObjectPublisher> signalHandler; ++ std::unordered_map<const QThread*, SignalHandler<QMetaObjectPublisher>> signalHandlers; ++ SignalHandler<QMetaObjectPublisher> *signalHandlerFor(const QObject *object); + + // true when the client is idle, false otherwise + bool clientIsIdle; +diff --git a/src/webchannel/signalhandler_p.h b/src/webchannel/signalhandler_p.h +index 27afadb..d77373c 100644 +--- a/src/webchannel/signalhandler_p.h ++++ b/src/webchannel/signalhandler_p.h +@@ -56,6 +56,7 @@ + #include <QVector> + #include <QMetaMethod> + #include <QDebug> ++#include <QThread> + + QT_BEGIN_NAMESPACE + +@@ -71,6 +72,7 @@ static const int s_destroyedSignalIndex = QObject::staticMetaObject.indexOfMetho + template<class Receiver> + class SignalHandler : public QObject + { ++ Q_DISABLE_COPY(SignalHandler) + public: + SignalHandler(Receiver *receiver, QObject *parent = 0); + +@@ -268,6 +270,7 @@ int SignalHandler<Receiver>::qt_metacall(QMetaObject::Call call, int methodId, v + if (call == QMetaObject::InvokeMetaMethod) { + const QObject *object = sender(); + Q_ASSERT(object); ++ Q_ASSERT(QThread::currentThread() == object->thread()); + Q_ASSERT(senderSignalIndex() == methodId); + Q_ASSERT(m_connectionsCounter.contains(object)); + Q_ASSERT(m_connectionsCounter.value(object).contains(methodId)); +diff --git a/tests/auto/webchannel/tst_webchannel.cpp b/tests/auto/webchannel/tst_webchannel.cpp +index 181da9e..7f846f5 100644 +--- a/tests/auto/webchannel/tst_webchannel.cpp ++++ b/tests/auto/webchannel/tst_webchannel.cpp +@@ -943,8 +943,6 @@ void TestWebChannel::testInfiniteRecursion() + + void TestWebChannel::testAsyncObject() + { +- QSKIP("This test is broken. See QTBUG-80729"); +- + QWebChannel channel; + channel.connectTo(m_dummyTransport); + +@@ -1082,7 +1080,7 @@ void TestWebChannel::benchInitializeClients() + + publisher->propertyUpdatesInitialized = false; + publisher->signalToPropertyMap.clear(); +- publisher->signalHandler.clear(); ++ publisher->signalHandlers.clear(); + } + } + +-- +2.36.0 + +From 01803a64b0a0b03eb8d9add60008829bc9d5c11e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?=C3=98ystein=20Heskestad?= <oystein.heskestad@qt.io> +Date: Fri, 7 May 2021 15:23:38 +0200 +Subject: [PATCH 2/3] Handle per-transport client idle status + +[ChangeLog][QMetaObjectPublisher] Handle per-transport client idle status + +Task-number: QTBUG-92927 +Change-Id: I5a06261e6dddb0fc0fae9f73b280c61cf5a2b52d +Reviewed-by: Arno Rehn <a.rehn@menlosystems.com> +(cherry picked from commit a7199de7d90f48ce3d95cae795bd9209c39516ce) +--- + src/webchannel/qmetaobjectpublisher.cpp | 71 ++++++++++++++++++------ + src/webchannel/qmetaobjectpublisher_p.h | 39 +++++++++++-- + tests/auto/qml/testwebchannel.cpp | 6 +- + tests/auto/webchannel/tst_webchannel.cpp | 48 +++++++++++++++- + tests/auto/webchannel/tst_webchannel.h | 1 + + 5 files changed, 138 insertions(+), 27 deletions(-) + +diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp +index e997b75..3897c07 100644 +--- a/src/webchannel/qmetaobjectpublisher.cpp ++++ b/src/webchannel/qmetaobjectpublisher.cpp +@@ -186,7 +186,6 @@ Q_DECLARE_TYPEINFO(OverloadResolutionCandidate, Q_MOVABLE_TYPE); + QMetaObjectPublisher::QMetaObjectPublisher(QWebChannel *webChannel) + : QObject(webChannel) + , webChannel(webChannel) +- , clientIsIdle(false) + , blockUpdates(false) + , propertyUpdatesInitialized(false) + { +@@ -300,17 +299,17 @@ QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object, QWeb + return data; + } + +-void QMetaObjectPublisher::setClientIsIdle(bool isIdle) ++void QMetaObjectPublisher::setClientIsIdle(bool isIdle, QWebChannelAbstractTransport *transport) + { +- if (clientIsIdle == isIdle) { +- return; +- } +- clientIsIdle = isIdle; +- if (!isIdle && timer.isActive()) { +- timer.stop(); +- } else if (isIdle && !timer.isActive()) { +- timer.start(PROPERTY_UPDATE_INTERVAL, this); +- } ++ transportState[transport].clientIsIdle = isIdle; ++ if (isIdle) ++ sendEnqueuedPropertyUpdates(transport); ++} ++ ++bool QMetaObjectPublisher::isClientIdle(QWebChannelAbstractTransport *transport) ++{ ++ auto found = transportState.find(transport); ++ return found != transportState.end() && found.value().clientIsIdle; + } + + QJsonObject QMetaObjectPublisher::initializeClient(QWebChannelAbstractTransport *transport) +@@ -365,7 +364,7 @@ void QMetaObjectPublisher::initializePropertyUpdates(const QObject *const object + + void QMetaObjectPublisher::sendPendingPropertyUpdates() + { +- if (blockUpdates || !clientIsIdle || pendingPropertyUpdates.isEmpty()) { ++ if (blockUpdates) { + return; + } + +@@ -415,18 +414,19 @@ void QMetaObjectPublisher::sendPendingPropertyUpdates() + + // data does not contain specific updates + if (!data.isEmpty()) { +- setClientIsIdle(false); +- + message[KEY_DATA] = data; +- broadcastMessage(message); ++ enqueueBroadcastMessage(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); ++ enqueueMessage(message, it.key()); + } ++ ++ for (auto state = transportState.begin(); state != transportState.end(); ++state) ++ sendEnqueuedPropertyUpdates(state.key()); + } + + QVariant QMetaObjectPublisher::invokeMethod(QObject *const object, const QMetaMethod &method, +@@ -572,7 +572,7 @@ void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signal + } + } else { + pendingPropertyUpdates[object][signalIndex] = arguments; +- if (clientIsIdle && !blockUpdates && !timer.isActive()) { ++ if (!blockUpdates && !timer.isActive()) { + timer.start(PROPERTY_UPDATE_INTERVAL, this); + } + } +@@ -852,6 +852,40 @@ void QMetaObjectPublisher::broadcastMessage(const QJsonObject &message) const + } + } + ++void QMetaObjectPublisher::enqueueBroadcastMessage(const QJsonObject &message) ++{ ++ if (webChannel->d_func()->transports.isEmpty()) { ++ qWarning("QWebChannel is not connected to any transports, cannot send message: %s", ++ QJsonDocument(message).toJson().constData()); ++ return; ++ } ++ ++ for (auto *transport : webChannel->d_func()->transports) { ++ auto &state = transportState[transport]; ++ state.queuedMessages.append(message); ++ } ++} ++ ++void QMetaObjectPublisher::enqueueMessage(const QJsonObject &message, ++ QWebChannelAbstractTransport *transport) ++{ ++ auto &state = transportState[transport]; ++ state.queuedMessages.append(message); ++} ++ ++void QMetaObjectPublisher::sendEnqueuedPropertyUpdates(QWebChannelAbstractTransport *transport) ++{ ++ auto found = transportState.find(transport); ++ if (found != transportState.end() && found.value().clientIsIdle ++ && !found.value().queuedMessages.isEmpty()) { ++ for (auto message : found.value().queuedMessages) { ++ transport->sendMessage(message); ++ } ++ found.value().queuedMessages.clear(); ++ found.value().clientIsIdle = false; ++ } ++} ++ + void QMetaObjectPublisher::handleMessage(const QJsonObject &message, QWebChannelAbstractTransport *transport) + { + if (!webChannel->d_func()->transports.contains(transport)) { +@@ -866,7 +900,7 @@ void QMetaObjectPublisher::handleMessage(const QJsonObject &message, QWebChannel + + const MessageType type = toType(message.value(KEY_TYPE)); + if (type == TypeIdle) { +- setClientIsIdle(true); ++ setClientIsIdle(true, transport); + } else if (type == TypeInit) { + if (!message.contains(KEY_ID)) { + qWarning("JSON message object is missing the id property: %s", +@@ -931,6 +965,7 @@ void QMetaObjectPublisher::setBlockUpdates(bool block) + blockUpdates = block; + + if (!blockUpdates) { ++ timer.start(PROPERTY_UPDATE_INTERVAL, this); + sendPendingPropertyUpdates(); + } else if (timer.isActive()) { + timer.stop(); +diff --git a/src/webchannel/qmetaobjectpublisher_p.h b/src/webchannel/qmetaobjectpublisher_p.h +index ded0d33..4713ef1 100644 +--- a/src/webchannel/qmetaobjectpublisher_p.h ++++ b/src/webchannel/qmetaobjectpublisher_p.h +@@ -59,6 +59,7 @@ + #include <QBasicTimer> + #include <QPointer> + #include <QJsonObject> ++#include <QQueue> + + #include <unordered_map> + +@@ -111,17 +112,36 @@ public: + */ + void broadcastMessage(const QJsonObject &message) const; + ++ /** ++ * Enqueue the given @p message to all known transports. ++ */ ++ void enqueueBroadcastMessage(const QJsonObject &message); ++ ++ /** ++ * Enqueue the given @p message to @p transport. ++ */ ++ void enqueueMessage(const QJsonObject &message, QWebChannelAbstractTransport *transport); ++ ++ /** ++ * If client for given @p transport is idle, send queued messaged to @p transport and then mark ++ * the client as not idle. ++ */ ++ void sendEnqueuedPropertyUpdates(QWebChannelAbstractTransport *transport); ++ + /** + * Serialize the QMetaObject of @p object and return it in JSON form. + */ + QJsonObject classInfoForObject(const QObject *object, QWebChannelAbstractTransport *transport); + + /** +- * Set the client to idle or busy, based on the value of @p isIdle. +- * +- * When the value changed, start/stop the property update timer accordingly. ++ * Set the client to idle or busy for a single @p transport, based on the value of @p isIdle. + */ +- void setClientIsIdle(bool isIdle); ++ void setClientIsIdle(bool isIdle, QWebChannelAbstractTransport *transport); ++ ++ /** ++ * Check that client is idle for @p transport. ++ */ ++ bool isClientIdle(QWebChannelAbstractTransport *transport); + + /** + * Initialize clients by sending them the class information of the registered objects. +@@ -277,8 +297,15 @@ private: + std::unordered_map<const QThread*, SignalHandler<QMetaObjectPublisher>> signalHandlers; + SignalHandler<QMetaObjectPublisher> *signalHandlerFor(const QObject *object); + +- // true when the client is idle, false otherwise +- bool clientIsIdle; ++ struct TransportState ++ { ++ TransportState() : clientIsIdle(false) { } ++ // true when the client is idle, false otherwise ++ bool clientIsIdle; ++ // messages to send ++ QQueue<QJsonObject> queuedMessages; ++ }; ++ QHash<QWebChannelAbstractTransport *, TransportState> transportState; + + // true when no property updates should be sent, false otherwise + bool blockUpdates; +diff --git a/tests/auto/qml/testwebchannel.cpp b/tests/auto/qml/testwebchannel.cpp +index 9891687..3ca81c2 100644 +--- a/tests/auto/qml/testwebchannel.cpp ++++ b/tests/auto/qml/testwebchannel.cpp +@@ -46,7 +46,11 @@ TestWebChannel::~TestWebChannel() + + bool TestWebChannel::clientIsIdle() const + { +- return QWebChannel::d_func()->publisher->clientIsIdle; ++ for (auto *transport : QWebChannel::d_func()->transports) { ++ if (QWebChannel::d_func()->publisher->isClientIdle(transport)) ++ return true; ++ } ++ return false; + } + + QT_END_NAMESPACE +diff --git a/tests/auto/webchannel/tst_webchannel.cpp b/tests/auto/webchannel/tst_webchannel.cpp +index 7f846f5..37f989a 100644 +--- a/tests/auto/webchannel/tst_webchannel.cpp ++++ b/tests/auto/webchannel/tst_webchannel.cpp +@@ -785,7 +785,7 @@ void TestWebChannel::testTransportWrapObjectProperties() + DummyTransport *dummyTransport = new DummyTransport(this); + channel.connectTo(dummyTransport); + channel.d_func()->publisher->initializeClient(dummyTransport); +- channel.d_func()->publisher->setClientIsIdle(true); ++ channel.d_func()->publisher->setClientIsIdle(true, dummyTransport); + + QCOMPARE(channel.d_func()->publisher->transportedWrappedObjects.size(), 0); + +@@ -988,6 +988,50 @@ void TestWebChannel::testAsyncObject() + thread.wait(); + } + ++void TestWebChannel::testPropertyMultipleTransports() ++{ ++ DummyTransport transport1; ++ DummyTransport transport2; ++ ++ QWebChannel channel; ++ QMetaObjectPublisher *publisher = channel.d_func()->publisher; ++ ++ TestObject testObj; ++ testObj.setObjectName("testObject"); ++ channel.registerObject(testObj.objectName(), &testObj); ++ channel.connectTo(&transport1); ++ channel.connectTo(&transport2); ++ ++ testObj.setProp("Hello"); ++ ++ publisher->initializeClient(&transport1); ++ publisher->initializeClient(&transport2); ++ publisher->setClientIsIdle(true, &transport1); ++ QCOMPARE(publisher->isClientIdle(&transport1), true); ++ QCOMPARE(publisher->isClientIdle(&transport2), false); ++ QVERIFY(transport1.messagesSent().isEmpty()); ++ QVERIFY(transport2.messagesSent().isEmpty()); ++ ++ testObj.setProp("World"); ++ QTRY_COMPARE_WITH_TIMEOUT(transport1.messagesSent().size(), 1u, 2000); ++ QCOMPARE(transport2.messagesSent().size(), 0u); ++ publisher->setClientIsIdle(true, &transport2); ++ QTRY_COMPARE_WITH_TIMEOUT(transport2.messagesSent().size(), 1u, 2000); ++ QCOMPARE(publisher->isClientIdle(&transport1), false); ++ QCOMPARE(publisher->isClientIdle(&transport2), false); ++ ++ testObj.setProp("!!!"); ++ publisher->setClientIsIdle(true, &transport2); ++ QCOMPARE(publisher->isClientIdle(&transport2), true); ++ QCOMPARE(publisher->isClientIdle(&transport1), false); ++ QTRY_COMPARE_WITH_TIMEOUT(transport2.messagesSent().size(), 2u, 2000); ++ QCOMPARE(transport1.messagesSent().size(), 1u); ++ publisher->setClientIsIdle(true, &transport1); ++ QTRY_COMPARE_WITH_TIMEOUT(transport1.messagesSent().size(), 2u, 2000); ++ QCOMPARE(publisher->isClientIdle(&transport1), false); ++ QCOMPARE(publisher->isClientIdle(&transport2), false); ++} ++ + class FunctionWrapper : public QObject + { + Q_OBJECT +@@ -1105,7 +1149,7 @@ void TestWebChannel::benchPropertyUpdates() + obj->change(); + } + +- channel.d_func()->publisher->clientIsIdle = true; ++ channel.d_func()->publisher->setClientIsIdle(true, m_dummyTransport); + channel.d_func()->publisher->sendPendingPropertyUpdates(); + } + } +diff --git a/tests/auto/webchannel/tst_webchannel.h b/tests/auto/webchannel/tst_webchannel.h +index eae21f4..dd4e690 100644 +--- a/tests/auto/webchannel/tst_webchannel.h ++++ b/tests/auto/webchannel/tst_webchannel.h +@@ -348,6 +348,7 @@ private slots: + void testJsonToVariant(); + void testInfiniteRecursion(); + void testAsyncObject(); ++ void testPropertyMultipleTransports(); + void testDeletionDuringMethodInvocation_data(); + void testDeletionDuringMethodInvocation(); + +-- +2.36.0 + +From 8c842152da613f941892481d62267c73c4a4f006 Mon Sep 17 00:00:00 2001 +From: Arno Rehn <a.rehn@menlosystems.com> +Date: Wed, 8 Dec 2021 22:44:49 +0100 +Subject: [PATCH 3/3] QMetaObjectPublisher: Never send stale queued messages + +If the client is connected with an in-process transport, it can happen +that a transmitted message triggers a subsequent property change. +In that case, we need to ensure that the queued messages have already +been cleared; otherwise the recursive call will send everythig again. + +Case in point: The qmlwebchannel tests fail if we don't clear the +queued messages before sending them out. + +For that same reason set the client to "busy" (aka non-idle) just right +before sending out the messages; otherwise a potential "Idle" type +message will not correctly restore the Idle state. + +Pick-to: 6.2 +Pick-to: 6.3 +Change-Id: Idc4afdd5cf4b4e03b8de8953a03d28442d74a3ab +Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> +(cherry picked from commit b4bf8f5e043120341cd9caa59f25a2beecd94ad0) +--- + src/webchannel/qmetaobjectpublisher.cpp | 18 +++++++++++++++--- + 1 file changed, 15 insertions(+), 3 deletions(-) + +diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp +index 3897c07..898d769 100644 +--- a/src/webchannel/qmetaobjectpublisher.cpp ++++ b/src/webchannel/qmetaobjectpublisher.cpp +@@ -878,11 +878,23 @@ void QMetaObjectPublisher::sendEnqueuedPropertyUpdates(QWebChannelAbstractTransp + auto found = transportState.find(transport); + if (found != transportState.end() && found.value().clientIsIdle + && !found.value().queuedMessages.isEmpty()) { +- for (auto message : found.value().queuedMessages) { ++ ++ // If the client is connected with an in-process transport, it can ++ // happen that a message triggers a subsequent property change. In ++ // that case, we need to ensure that the queued messages have already ++ // been cleared; otherwise the recursive call will send everythig again. ++ // Case in point: The qmlwebchannel tests fail if we don't clear the ++ // queued messages before sending them out. ++ // For that same reason set the client to "busy" (aka non-idle) just ++ // right before sending out the messages; otherwise a potential ++ // "Idle" type message will not correctly restore the Idle state. ++ const auto messages = std::move(found.value().queuedMessages); ++ Q_ASSERT(found.value().queuedMessages.isEmpty()); ++ found.value().clientIsIdle = false; ++ ++ for (const auto &message : messages) { + transport->sendMessage(message); + } +- found.value().queuedMessages.clear(); +- found.value().clientIsIdle = false; + } + } + +-- +2.36.0 + |