diff options
| author | Ulf Hermann <ulf.hermann@qt.io> | 2025-06-30 10:47:40 +0200 |
|---|---|---|
| committer | Ulf Hermann <ulf.hermann@qt.io> | 2025-07-04 14:21:06 +0200 |
| commit | 941c3b05e3234e6f49783821a71d6a137f47cc68 (patch) | |
| tree | 2da2eb087605c7172e77db131ee70d09a26c7f9d | |
| parent | e8eb603376e1632c32a9363d2a24297cbb72d08a (diff) | |
Loader: Also drop engine when clearing contexts recursively
Simply clearing the expressions is not enough. We might have internal
signal/slot connections in addition. The main protagonists of such
behavior listen to context invalidation, though, and a context without
engine counts as invalid. At the same time, we leave the context object
in place so that we don't re-open QTBUG-66822. The currently running
binding will still be able to complete this way.
Fixes: QTBUG-118188
Change-Id: Ia973be26f0e4e3244adc71f95df7e0b4bef412c3
Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
(cherry picked from commit 054873dfea0ce07c94bb603edc3b7d61a6190c4f)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 6b57107d908df535b04357e4fa9a716a03547429)
(cherry picked from commit f68cfe5ceb903f2091b2ce23032823a863f184c7)
(cherry picked from commit 26481958d806b2ca2f144a2667dc38e59df6ba7f)
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
| -rw-r--r-- | src/qml/qml/qqmlcontextdata.cpp | 6 | ||||
| -rw-r--r-- | tests/auto/quick/qquickloader/data/invalidateContext.qml | 28 | ||||
| -rw-r--r-- | tests/auto/quick/qquickloader/tst_qquickloader.cpp | 56 |
3 files changed, 87 insertions, 3 deletions
diff --git a/src/qml/qml/qqmlcontextdata.cpp b/src/qml/qml/qqmlcontextdata.cpp index 6dc2f22a23..64fb3a5d30 100644 --- a/src/qml/qml/qqmlcontextdata.cpp +++ b/src/qml/qml/qqmlcontextdata.cpp @@ -100,7 +100,6 @@ void QQmlContextData::invalidate() } m_importedScripts.clear(); - m_engine = nullptr; clearParent(); } @@ -111,6 +110,8 @@ void QQmlContextData::clearContextRecursively() for (auto ctxIt = m_childContexts; ctxIt; ctxIt = ctxIt->m_nextChild) ctxIt->clearContextRecursively(); + + m_engine = nullptr; } void QQmlContextData::clearContext() @@ -137,8 +138,7 @@ QQmlContextData::~QQmlContextData() // avoid recursion addref(); - if (m_engine) - invalidate(); + invalidate(); m_linkedContext.reset(); Q_ASSERT(refCount() == 1); diff --git a/tests/auto/quick/qquickloader/data/invalidateContext.qml b/tests/auto/quick/qquickloader/data/invalidateContext.qml new file mode 100644 index 0000000000..b5a28f5d0f --- /dev/null +++ b/tests/auto/quick/qquickloader/data/invalidateContext.qml @@ -0,0 +1,28 @@ +import QtQuick + +Item { + Loader { + active: loaderActive + sourceComponent: comp + } + + Component { + id: comp + Column { + Repeater { + id: repeater + model: cppModel + + Component.onCompleted: console.log("Repeater constructed"); + Component.onDestruction: console.log("Repeater destroyed"); + + delegate: Text { + text: { + console.log("updating text"); + return display + rootData.getValue(); + } + } + } + } + } +} diff --git a/tests/auto/quick/qquickloader/tst_qquickloader.cpp b/tests/auto/quick/qquickloader/tst_qquickloader.cpp index 389f037166..afc49fe74d 100644 --- a/tests/auto/quick/qquickloader/tst_qquickloader.cpp +++ b/tests/auto/quick/qquickloader/tst_qquickloader.cpp @@ -117,6 +117,8 @@ private slots: void stackOverflow(); void stackOverflow2(); void boundComponent(); + + void invalidateContext(); }; Q_DECLARE_METATYPE(QList<QQmlError>) @@ -1585,6 +1587,60 @@ void tst_QQuickLoader::boundComponent() QCOMPARE(o->objectName(), QStringLiteral("loaded")); } +class CppModel : public QAbstractListModel +{ +public: + virtual int rowCount(const QModelIndex &) const override {return 1;} + virtual QVariant data(const QModelIndex &, int) const override { return "test"; } + + void reset () {beginResetModel(); endResetModel();} +}; + +class RootData : public QObject +{ + Q_OBJECT +public: + RootData() : object(std::make_unique<QObject>()) + { + object->setObjectName("objectName"); + } + + Q_INVOKABLE QString getValue() const { return object->objectName(); } + + void deleteObject() { object.reset(); } + +private: + std::unique_ptr<QObject> object; +}; + +void tst_QQuickLoader::invalidateContext() +{ + CppModel model; + RootData rootData; + + QQmlEngine engine; + QQmlContext *rootContext = engine.rootContext(); + rootContext->setContextProperty("cppModel", &model); + rootContext->setContextProperty("loaderActive", true); + rootContext->setContextProperty("rootData", &rootData); + + QQmlComponent component(&engine, testFileUrl("invalidateContext.qml"));\ + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + + QTest::ignoreMessage(QtDebugMsg, "Repeater constructed"); + QTest::ignoreMessage(QtDebugMsg, "updating text"); + QScopedPointer<QObject> o(component.create()); + + QTest::ignoreMessage(QtDebugMsg, "Repeater destroyed"); + rootContext->setContextProperty("loaderActive", false); + + // The object should never be used anymore, as loader is inactive + rootData.deleteObject(); + + // Should not trigger any re-evaluation (which would crash in getValue()) + model.reset(); +} + QTEST_MAIN(tst_QQuickLoader) #include "tst_qquickloader.moc" |
