aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2025-06-30 10:47:40 +0200
committerUlf Hermann <ulf.hermann@qt.io>2025-07-04 14:21:06 +0200
commit941c3b05e3234e6f49783821a71d6a137f47cc68 (patch)
tree2da2eb087605c7172e77db131ee70d09a26c7f9d
parente8eb603376e1632c32a9363d2a24297cbb72d08a (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.cpp6
-rw-r--r--tests/auto/quick/qquickloader/data/invalidateContext.qml28
-rw-r--r--tests/auto/quick/qquickloader/tst_qquickloader.cpp56
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"